From 2b0254523ae34732681f87ce99eb2523c94394df Mon Sep 17 00:00:00 2001 From: Justin Ackers Date: Mon, 15 May 2023 16:09:13 +0200 Subject: [PATCH 001/168] removed RP-specific TX channel params --- src/Devices/DAQ/DAQ.jl | 51 ++++++++++++--------- src/Devices/DAQ/RedPitayaDAQ.jl | 80 ++++++++------------------------- 2 files changed, 48 insertions(+), 83 deletions(-) diff --git a/src/Devices/DAQ/DAQ.jl b/src/Devices/DAQ/DAQ.jl index 64fcd8f9..29f57bc3 100644 --- a/src/Devices/DAQ/DAQ.jl +++ b/src/Devices/DAQ/DAQ.jl @@ -61,44 +61,53 @@ Base.@kwdef struct DAQTxChannelParams <: TxChannelParams sinkImpedance::SinkImpedance = SINK_HIGH allowedWaveforms::Vector{Waveform} = [WAVEFORM_SINE] feedback::Union{DAQFeedback, Nothing} = nothing - calibration::Union{typeof(1.0u"V/T"), Nothing} = nothing + calibration::Union{typeof(1.0u"V/T"), typeof(1.0u"V/A"), Nothing} = nothing end Base.@kwdef struct DAQRxChannelParams <: RxChannelParams channelIdx::Int64 end -"Create DAQ channel description from device dict part." -function createDAQChannels(dict::Dict{String, Any}) - channels = Dict{String, DAQChannelParams}() - for (key, value) in dict +function createDAQChannel(::Type{DAQTXChannelParams}, dict::Dict{String,Any}) splattingDict = Dict{Symbol, Any}() - if value["type"] == "tx" - splattingDict[:channelIdx] = value["channel"] - splattingDict[:limitPeak] = uparse(value["limitPeak"]) + splattingDict[:channelIdx] = dict["channel"] + splattingDict[:limitPeak] = uparse(dict["limitPeak"]) - if haskey(value, "sinkImpedance") - splattingDict[:sinkImpedance] = value["sinkImpedance"] == "FIFTY_OHM" ? SINK_FIFTY_OHM : SINK_HIGH + if haskey(dict, "sinkImpedance") + splattingDict[:sinkImpedance] = dict["sinkImpedance"] == "FIFTY_OHM" ? SINK_FIFTY_OHM : SINK_HIGH end - if haskey(value, "allowedWaveforms") - splattingDict[:allowedWaveforms] = toWaveform.(value["allowedWaveforms"]) + if haskey(dict, "allowedWaveforms") + splattingDict[:allowedWaveforms] = toWaveform.(dict["allowedWaveforms"]) end - if haskey(value, "feedback") - channelID=value["feedback"]["channelID"] - calibration=uparse(value["feedback"]["calibration"]) - + if haskey(dict, "feedback") + channelID = dict["feedback"]["channelID"] + calibration = uparse(dict["feedback"]["calibration"]) # TODO/JA: change parsing to allow Transfer Function filename (TransferFunction(...)) splattingDict[:feedback] = DAQFeedback(channelID=channelID, calibration=calibration) end - if haskey(value, "calibration") - splattingDict[:calibration] = uparse.(value["calibration"]) + if haskey(dict, "calibration") + splattingDict[:calibration] = uparse.(dict["calibration"]) # TODO/JA: tx calibration end - channels[key] = DAQTxChannelParams(;splattingDict...) + return DAQTxChannelParams(;splattingDict...) +end + + + +createDAQChannel(::Type{DAQRxChannelParams}, value) = DAQRxChannelParams(channelIdx=value["channel"]) + +"Create DAQ channel description from device dict part." +function createDAQChannels(dict::Dict{String, Any}) + channels = Dict{String, DAQChannelParams}() + for (key, value) in dict + if value["type"] == "tx" + channels[key] = createDAQChannel(DAQTxChannelParams, value) elseif value["type"] == "rx" - channels[key] = DAQRxChannelParams(channelIdx=value["channel"]) + channels[key] = createDAQChannel(DAQRxChannelParams, value) + elseif value["type"] == "tx_slow" + channels[key] = createDAQChannel(RedPitayaLUTChannelParams, value) end end @@ -128,7 +137,7 @@ function createDAQParams(DAQType::Type{T}, dict::Dict{String, Any}) where {T <: splattingDict = dict_to_splatting(mainDict) splattingDict[:channels] = createDAQChannels(DAQType, channelDict) - + # TODO: check if DAQ type can actually support all types of channels try return DAQType(;splattingDict...) catch e diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index c70374a9..3ea28610 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -15,74 +15,30 @@ Base.@kwdef mutable struct RedPitayaDAQParams <: DAQParams rampingFraction::Float32 = 1.0 end -Base.@kwdef struct RedPitayaTxChannelParams <: TxChannelParams - channelIdx::Int64 - limitPeak::typeof(1.0u"V") - sinkImpedance::SinkImpedance = SINK_HIGH - allowedWaveforms::Vector{Waveform} = [WAVEFORM_SINE] - feedback::Union{DAQFeedback, Nothing} = nothing - calibration::Union{typeof(1.0u"V/T"), typeof(1.0u"V/A"), Nothing} = nothing -end - Base.@kwdef struct RedPitayaLUTChannelParams <: TxChannelParams channelIdx::Int64 calibration::Union{typeof(1.0u"V/T"), typeof(1.0u"V/A"), Nothing} = nothing end -"Create the params struct from a dict. Typically called during scanner instantiation." -function RedPitayaDAQParams(dict::Dict{String, Any}) - return createDAQParams(RedPitayaDAQParams, dict) -end - -function createDAQChannels(::Type{RedPitayaDAQParams}, dict::Dict{String, Any}) - # TODO This is mostly copied from createDAQChannels, maybe manage to get rid of the duplication - channels = Dict{String, DAQChannelParams}() - for (key, value) in dict - splattingDict = Dict{Symbol, Any}() - if value["type"] == "tx" - splattingDict[:channelIdx] = value["channel"] - splattingDict[:limitPeak] = uparse(value["limitPeak"]) - - if haskey(value, "sinkImpedance") - splattingDict[:sinkImpedance] = value["sinkImpedance"] == "FIFTY_OHM" ? SINK_FIFTY_OHM : SINK_HIGH - end - - if haskey(value, "allowedWaveforms") - splattingDict[:allowedWaveforms] = toWaveform.(value["allowedWaveforms"]) - end +function createDAQChannel(RedPitayaLUTChannelParams, dict::Dict{String, Any}) + calib = nothing + if haskey(dict, "calibration") + calib = uparse.(dict["calibration"]) - if haskey(value, "feedback") - channelID=value["feedback"]["channelID"] - calibration=uparse(value["feedback"]["calibration"]) - - splattingDict[:feedback] = DAQFeedback(channelID=channelID, calibration=calibration) - end - - if haskey(value, "calibration") - splattingDict[:calibration] = uparse.(value["calibration"]) - end - - channels[key] = RedPitayaTxChannelParams(;splattingDict...) - elseif value["type"] == "rx" - channels[key] = DAQRxChannelParams(channelIdx=value["channel"]) - elseif value["type"] == "txSlow" - calib = nothing - if haskey(value, "calibration") - calib = uparse.(value["calibration"]) - - if unit(upreferred(calib)) == upreferred(u"V/T") - calib = calib .|> u"V/T" - elseif unit(upreferred(calib)) == upreferred(u"V/A") - calib = calib .|> u"V/A" - else - error("The values have to be either given as a V/t or in V/A. You supplied the type `$(eltype(calib))`.") - end - end - channels[key] = RedPitayaLUTChannelParams(channelIdx=value["channel"], calibration = calib) + if unit(upreferred(calib)) == upreferred(u"V/T") + calib = calib .|> u"V/T" + elseif unit(upreferred(calib)) == upreferred(u"V/A") + calib = calib .|> u"V/A" + else + error("The values have to be either given as a V/T or in V/A. You supplied the type `$(eltype(calib))`.") end end + return RedPitayaLUTChannelParams(channelIdx=dict["channel"], calibration = calib) +end - return channels +"Create the params struct from a dict. Typically called during scanner instantiation." +function RedPitayaDAQParams(dict::Dict{String, Any}) + return createDAQParams(RedPitayaDAQParams, dict) end export RedPitayaDAQ @@ -164,7 +120,7 @@ function setRampingParams(daq::RedPitayaDAQ, sequence::Sequence) for channel in txChannels m = nothing idx = channel[2].channelIdx - if channel[2] isa RedPitayaTxChannelParams + if channel[2] isa TxChannelParams m = idx elseif channel[2] isa RedPitayaLUTChannelParams # Map to fast DAC @@ -636,7 +592,7 @@ function setupRx(daq::RedPitayaDAQ, sequence::Sequence) # TODO possibly move some of this into abstract daq daq.refChanIDs = [] - txChannels = [channel[2] for channel in daq.params.channels if channel[2] isa RedPitayaTxChannelParams] + txChannels = [channel[2] for channel in daq.params.channels if channel[2] isa TxChannelParams] daq.refChanIDs = unique([tx.feedback.channelID for tx in txChannels if !isnothing(tx.feedback)]) # Construct view to save bandwidth @@ -707,7 +663,7 @@ function prepareTx(daq::RedPitayaDAQ, sequence::Sequence) for comp in periodicElectricalComponents(channel) # Lengths check == 1 happens in setupTx already amp = amplitude(comp) - if dimension(amp) == dimension(1.0u"T") + if dimension(amp) != dimension(1.0u"V") amp = (amp * calibration(daq, name)) end push!(amps, amp) From 7f9e910fc1afc45943d6d9aea0f7dd2eaead0def Mon Sep 17 00:00:00 2001 From: Justin Ackers Date: Tue, 16 May 2023 17:55:19 +0200 Subject: [PATCH 002/168] add path to configFile to scanner and device --- src/Devices/Device.jl | 14 ++++++++++++++ src/Scanner.jl | 18 ++++++++++++------ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/Devices/Device.jl b/src/Devices/Device.jl index 533f6282..5a539a5a 100644 --- a/src/Devices/Device.jl +++ b/src/Devices/Device.jl @@ -33,6 +33,8 @@ macro add_device_fields(paramType) present::Bool = false #"Vector of dependencies for this device." dependencies::Dict{String,Union{Device,Missing}} + #"Path of the config used to load the device (either Scanner.toml or Device.toml)" + configFile::String end) end @@ -51,6 +53,18 @@ isPresent(device::Device) = device.present "Retrieve the dependencies of a device." dependencies(device::Device) = device.dependencies +"Retrieve the configuration file of a device." +configFile(device::Device) = device.configFile + +"Retrieve the configuration directory of the scanner." +function configDir(device::Device) + if basename(device.configFile) == "Scanner.toml" + return dirname(device.configFile) # if the device is read from a Scanner.toml the directory is the scanner directory + else + return joinpath(splitpath(device.configFile)[1:end-2]...) # otherwise the Device.toml is in the Devices folder + end +end + "Retrieve all dependencies of a certain type." dependencies(device::Device, type::DataType) = [dependency for dependency in Base.values(dependencies(device)) if dependency isa type] diff --git a/src/Scanner.jl b/src/Scanner.jl index d0404d21..280ed185 100644 --- a/src/Scanner.jl +++ b/src/Scanner.jl @@ -52,15 +52,18 @@ function initiateDevices(configDir::AbstractString, devicesParams::Dict{String, # Get implementations for all devices in the specified order for deviceID in devicesParams["initializationOrder"] params = nothing + configFile = nothing if haskey(devicesParams, deviceID) params = devicesParams[deviceID] + configFile = joinpath(configDir, "Scanner.toml") else params = deviceParams(configDir, deviceID) + configFile = joinpath(configDir, "Devices", deviceID*".toml") end if !isnothing(params) deviceType = pop!(params, "deviceType") - + dependencies_ = Dict{String, Union{Device, Missing}}() if haskey(params, "dependencies") deviceDepencencies = pop!(params, "dependencies") @@ -80,7 +83,7 @@ function initiateDevices(configDir::AbstractString, devicesParams::Dict{String, error("Could not find a fitting device parameter struct for device ID `$deviceID`.") end - devices[deviceID] = DeviceImpl(deviceID=deviceID, params=paramsInst, dependencies=dependencies_) # All other fields must have default values! + devices[deviceID] = DeviceImpl(deviceID=deviceID, params=paramsInst, dependencies=dependencies_, configFile=configFile) # All other fields must have default values! else throw(ScannerConfigurationError("The device ID `$deviceID` was not found in the configuration. Please check your configuration.")) end @@ -190,8 +193,8 @@ Basic description of a scanner. mutable struct MPIScanner "Name of the scanner" name::String - "Path to the used configuration directory." - configDir::String + "Path to the used configuration file." + configFile::String "General parameters of the scanner like its bore size or gradient." generalParams::MPIScannerGeneral "Device instances instantiated by the scanner from its configuration." @@ -226,7 +229,7 @@ mutable struct MPIScanner @assert generalParams.name == name "The folder name and the scanner name in the configuration do not match." devices = initiateDevices(configDir, params["Devices"], robust = robust) - scanner = new(name, configDir, generalParams, devices) + scanner = new(name, filename, generalParams, devices) return scanner end @@ -246,8 +249,11 @@ end "Name of the scanner" name(scanner::MPIScanner) = scanner.name +"Path to the used configuration file" +configFile(scanner::MPIScanner) = scanner.configFile + "Path to the used configuration directory." -configDir(scanner::MPIScanner) = scanner.configDir +configDir(scanner::MPIScanner) = dirname(scanner.configFile) "General parameters of the scanner like its bore size or gradient." generalParams(scanner::MPIScanner) = scanner.generalParams From 9d8396634f53d17ee8ff9a18914383a05653a2fd Mon Sep 17 00:00:00 2001 From: Justin Ackers Date: Wed, 17 May 2023 18:36:32 +0200 Subject: [PATCH 003/168] first version to allow more complex calibration --- src/Devices/DAQ/DAQ.jl | 47 +++++++++++++++++++-------- src/Devices/DAQ/RedPitayaDAQ.jl | 24 +++++++++++--- src/Devices/DAQ/SimpleSimulatedDAQ.jl | 6 ++-- src/Protocols/Storage/MDF.jl | 2 +- 4 files changed, 57 insertions(+), 22 deletions(-) diff --git a/src/Devices/DAQ/DAQ.jl b/src/Devices/DAQ/DAQ.jl index 29f57bc3..f75c011d 100644 --- a/src/Devices/DAQ/DAQ.jl +++ b/src/Devices/DAQ/DAQ.jl @@ -52,7 +52,7 @@ abstract type RxChannelParams <: DAQChannelParams end Base.@kwdef struct DAQFeedback channelID::AbstractString - calibration::Union{typeof(1.0u"T/V"), Nothing} = nothing + calibration::Union{TransferFunction, String, Nothing} = nothing end Base.@kwdef struct DAQTxChannelParams <: TxChannelParams @@ -61,7 +61,7 @@ Base.@kwdef struct DAQTxChannelParams <: TxChannelParams sinkImpedance::SinkImpedance = SINK_HIGH allowedWaveforms::Vector{Waveform} = [WAVEFORM_SINE] feedback::Union{DAQFeedback, Nothing} = nothing - calibration::Union{typeof(1.0u"V/T"), typeof(1.0u"V/A"), Nothing} = nothing + calibration::Union{TransferFunction, String, Nothing} = nothing end Base.@kwdef struct DAQRxChannelParams <: RxChannelParams @@ -69,32 +69,40 @@ Base.@kwdef struct DAQRxChannelParams <: RxChannelParams end function createDAQChannel(::Type{DAQTXChannelParams}, dict::Dict{String,Any}) - splattingDict = Dict{Symbol, Any}() + splattingDict = Dict{Symbol, Any}() splattingDict[:channelIdx] = dict["channel"] splattingDict[:limitPeak] = uparse(dict["limitPeak"]) if haskey(dict, "sinkImpedance") splattingDict[:sinkImpedance] = dict["sinkImpedance"] == "FIFTY_OHM" ? SINK_FIFTY_OHM : SINK_HIGH - end + end if haskey(dict, "allowedWaveforms") splattingDict[:allowedWaveforms] = toWaveform.(dict["allowedWaveforms"]) - end + end if haskey(dict, "feedback") channelID = dict["feedback"]["channelID"] - calibration = uparse(dict["feedback"]["calibration"]) # TODO/JA: change parsing to allow Transfer Function filename (TransferFunction(...)) - splattingDict[:feedback] = DAQFeedback(channelID=channelID, calibration=calibration) - end + calibration_tf = parse_into_tf(dict["feedback"]["calibration"]) + splattingDict[:feedback] = DAQFeedback(channelID=channelID, calibration=calibration_tf) + end if haskey(dict, "calibration") - splattingDict[:calibration] = uparse.(dict["calibration"]) # TODO/JA: tx calibration - end + splattingDict[:calibration] = parse_info_tf(dict["calibration"]) + end return DAQTxChannelParams(;splattingDict...) end - +function parse_into_tf(value::String) + if occursin(".h5", value) # case 1: filename to transfer function, the TF will be read the first time calibration() is called, (done in _init(), to prevent delays while using the device) + calibration_tf = value + else # case 2: single value, extended into transfer function with no frequency dependency + calibration_value = uparse(value) + calibration_tf = TransferFunction([0,10e6],[ustrip(calibration_value), ustrip(calibration_value)], units=[unit(calibration_value)]) + end + return calibration_tf +end createDAQChannel(::Type{DAQRxChannelParams}, value) = DAQRxChannelParams(channelIdx=value["channel"]) @@ -202,8 +210,21 @@ allowedWaveforms(daq::AbstractDAQ, channelID::AbstractString) = channel(daq, cha isWaveformAllowed(daq::AbstractDAQ, channelID::AbstractString, waveform::Waveform) = waveform in allowedWaveforms(daq, channelID) feedback(daq::AbstractDAQ, channelID::AbstractString) = channel(daq, channelID).feedback feedbackChannelID(daq::AbstractDAQ, channelID::AbstractString) = feedback(daq, channelID).channelID -feedbackCalibration(daq::AbstractDAQ, channelID::AbstractString) = feedback(daq, channelID).calibration -calibration(daq::AbstractDAQ, channelID::AbstractString) = channel(daq, channelID).calibration +function feedbackCalibration(daq::AbstractDAQ, channelID::AbstractString) + if isa(feedback(daq, channelID).calibration, String) # if TF has not been loaded yet, load the h5 file + feedback(daq, channelID).calibration = TransferFunction(joinpath(configDir(daq),"TransferFunctions",feedback(daq, channelID).calibration)) + else + return feedback(daq, channelID).calibration + end +end +function calibration(daq::AbstractDAQ, channelID::AbstractString) + if isa(channel(daq, channelID).calibration, String) # if TF has not been loaded yet, load the h5 file + channel(daq, channelID).calibration = TransferFunction(joinpath(configDir(daq),"TransferFunctions",channel(daq, channelID).calibration)) + else + return channel(daq, channelID).calibration + end +end + diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index 3ea28610..38ee3b01 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -65,6 +65,14 @@ Base.@kwdef mutable struct RedPitayaDAQ <: AbstractDAQ end function _init(daq::RedPitayaDAQ) + # force all calibration transfer functions to be read from disk + for channelID in keys(daq.params.channels) + if isa(channel(daq, channelID), DAQTxChannelParams) + calibration(daq, channelID) + feedbackCalibration(daq, channelID) + end + end + # Restart the DAQ if necessary try daq.rpc = RedPitayaCluster(daq.params.ips, triggerMode = daq.params.triggerMode) @@ -521,7 +529,7 @@ function setupTxChannel(daq::RedPitayaDAQ, channel::PeriodicElectricalChannel, b end function setupTxChannel!(batch::ScpiBatch, daq::RedPitayaDAQ, channel::PeriodicElectricalChannel, baseFreq) channelIdx_ = channelIdx(daq, id(channel)) # Get index from scanner(!) channel - offsetVolts = offset(channel)*calibration(daq, id(channel)) + offsetVolts = offset(channel)*calibration(daq, id(channel))(0) # use DC value for offsets @add_batch batch offsetDAC!(daq.rpc, channelIdx_, ustrip(u"V", offsetVolts)) for (idx, component) in enumerate(components(channel)) setupTxComponent!(batch, daq, channel, component, idx, baseFreq) @@ -663,19 +671,25 @@ function prepareTx(daq::RedPitayaDAQ, sequence::Sequence) for comp in periodicElectricalComponents(channel) # Lengths check == 1 happens in setupTx already amp = amplitude(comp) + pha = phase(comp) if dimension(amp) != dimension(1.0u"V") - amp = (amp * calibration(daq, name)) + f_comp = ustrip(u"Hz", txBaseFrequency(sequence)) / divider(comp) + complex_comp = (amp*exp(im*pha)) * calibration(daq, name)(f_comp) + amp = abs(complex_comp) + pha = angle(complex_comp) end push!(amps, amp) - push!(phases, phase(comp)) + push!(phases, pha) end comps = arbitraryElectricalComponents(channel) if length(comps) > 2 throw(ScannerConfigurationError("Channel $(id(channel)) defines more than one arbitrary electrical component, which is not supported.")) elseif length(comps) == 1 wave = values(comps[1]) - if dimension(wave[1]) != dimension(1.0u"V") - wave = wave.*calibration(daq, name) + if dimension(wave[1]) != dimension(1.0u"V") # TODO/JA: make sure that the controller always uses V as its component waveform + f_comp = ustrip(u"Hz", txBaseFrequency(sequence)) / divider(comp) + f_awg = rfftfreq(2^14, f_comp*2^14) + wave = irfft(rfft(wave).*calibration(daq, name)(f_awg), 2^14) end allAwg[name] = wave end diff --git a/src/Devices/DAQ/SimpleSimulatedDAQ.jl b/src/Devices/DAQ/SimpleSimulatedDAQ.jl index 942b1fff..f4e30be3 100644 --- a/src/Devices/DAQ/SimpleSimulatedDAQ.jl +++ b/src/Devices/DAQ/SimpleSimulatedDAQ.jl @@ -184,7 +184,7 @@ function readDataPeriods(daq::SimpleSimulatedDAQ, startPeriod::Integer, numPerio if dimension(daq.amplitude[1]) == dimension(u"T") factor = 1.0u"T/T" else - factor = 1/scannerChannel.calibration + factor = 1/scannerChannel.calibration # TODO/JA: figure out how to include change here end temperatureRise = daq.params.temperatureRise[sendChannelID] @@ -215,12 +215,12 @@ function readDataPeriods(daq::SimpleSimulatedDAQ, startPeriod::Integer, numPerio Bᵢ = Bₘₐₓ.*sin.(2π*f*t.+ϕ) # Desired, ideal field without drift Bᵣ = (Bₘₐₓ.+ΔB).*sin.(2π*f*t.+ϕ.+Δϕ) # Field with drift of phase and amplitude - uₜₓ .+= Bᵢ.*sendChannel.calibration + uₜₓ .+= Bᵢ.*sendChannel.calibration # TODO/JA: figure out how to include change here uᵣₓ .+= simulateLangevinInduced(t, Bᵣ, f, ϕ.+Δϕ) # f is not completely correct due to the phase change, but this is only a rough approximation anyways # Assumes the same induced voltage from the field as given out with uₜₓ, # just with a slight change in phase and amplitude - uᵣₑ .+= Bᵣ.*sendChannel.calibration + uᵣₑ .+= Bᵣ.*sendChannel.calibration # TODO/JA: figure out how to include change here end # Assumes one reference and one measurement channel for each send channel diff --git a/src/Protocols/Storage/MDF.jl b/src/Protocols/Storage/MDF.jl index 07cdae14..f1ad749d 100644 --- a/src/Protocols/Storage/MDF.jl +++ b/src/Protocols/Storage/MDF.jl @@ -303,7 +303,7 @@ function fillMDFAcquisition(mdf::MDFv2InMemory, scanner::MPIScanner, sequence::S numFreq = div(numSamplingPoints_,2)+1 freq = collect(0:(numFreq-1))./(numFreq-1).*ustrip(u"Hz", rxBandwidth(sequence)) tf_ = TransferFunction(scanner) - tf = tf_[freq,1:numRxChannels_] + tf = tf_(freq,1:numRxChannels_) # TODO/JA: check if sampleTF can be used here! MPIFiles.rxTransferFunction(mdf, tf) MPIFiles.rxInductionFactor(mdf, tf_.inductionFactor) end From a0e60495d51ce062b8f67df9e26030fcdb88213c Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Tue, 4 Jul 2023 18:10:52 +0200 Subject: [PATCH 004/168] impedance aware sine tx controller working --- src/Devices/DAQ/DAQ.jl | 10 +++---- src/Devices/DAQ/RedPitayaDAQ.jl | 6 ++-- src/Devices/Virtual/TxDAQController.jl | 38 ++++++++++++++++++-------- 3 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/Devices/DAQ/DAQ.jl b/src/Devices/DAQ/DAQ.jl index f75c011d..84f5863b 100644 --- a/src/Devices/DAQ/DAQ.jl +++ b/src/Devices/DAQ/DAQ.jl @@ -50,12 +50,12 @@ abstract type DAQChannelParams end abstract type TxChannelParams <: DAQChannelParams end abstract type RxChannelParams <: DAQChannelParams end -Base.@kwdef struct DAQFeedback +Base.@kwdef mutable struct DAQFeedback channelID::AbstractString calibration::Union{TransferFunction, String, Nothing} = nothing end -Base.@kwdef struct DAQTxChannelParams <: TxChannelParams +Base.@kwdef mutable struct DAQTxChannelParams <: TxChannelParams channelIdx::Int64 limitPeak::typeof(1.0u"V") sinkImpedance::SinkImpedance = SINK_HIGH @@ -68,7 +68,7 @@ Base.@kwdef struct DAQRxChannelParams <: RxChannelParams channelIdx::Int64 end -function createDAQChannel(::Type{DAQTXChannelParams}, dict::Dict{String,Any}) +function createDAQChannel(::Type{DAQTxChannelParams}, dict::Dict{String,Any}) splattingDict = Dict{Symbol, Any}() splattingDict[:channelIdx] = dict["channel"] splattingDict[:limitPeak] = uparse(dict["limitPeak"]) @@ -88,7 +88,7 @@ function createDAQChannel(::Type{DAQTXChannelParams}, dict::Dict{String,Any}) end if haskey(dict, "calibration") - splattingDict[:calibration] = parse_info_tf(dict["calibration"]) + splattingDict[:calibration] = parse_into_tf(dict["calibration"]) end return DAQTxChannelParams(;splattingDict...) @@ -99,7 +99,7 @@ function parse_into_tf(value::String) calibration_tf = value else # case 2: single value, extended into transfer function with no frequency dependency calibration_value = uparse(value) - calibration_tf = TransferFunction([0,10e6],[ustrip(calibration_value), ustrip(calibration_value)], units=[unit(calibration_value)]) + calibration_tf = TransferFunction([0,10e6],ComplexF64[ustrip(calibration_value), ustrip(calibration_value)], units=[unit(calibration_value)]) end return calibration_tf end diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index 38ee3b01..bdec9158 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -20,7 +20,7 @@ Base.@kwdef struct RedPitayaLUTChannelParams <: TxChannelParams calibration::Union{typeof(1.0u"V/T"), typeof(1.0u"V/A"), Nothing} = nothing end -function createDAQChannel(RedPitayaLUTChannelParams, dict::Dict{String, Any}) +function createDAQChannel(::Type{RedPitayaLUTChannelParams}, dict::Dict{String, Any}) calib = nothing if haskey(dict, "calibration") calib = uparse.(dict["calibration"]) @@ -438,7 +438,7 @@ function startProducer(channel::Channel, daq::RedPitayaDAQ, numFrames) readSamples(rpu, startSample, samplesToRead, channel, chunkSize = chunkSize) catch e @info "Attempting reconnect to reset pipeline" - daq.rpc = RedPitayaCluster(daq.params.ips; triggerMode_=daq.params.triggerMode) + daq.rpc = RedPitayaCluster(daq.params.ips; triggerMode=daq.params.triggerMode) if serverMode(daq.rpc) == ACQUISITION for ch in daq.rampingChannel enableRampDown!(daq.rpc, ch, true) @@ -697,7 +697,7 @@ function prepareTx(daq::RedPitayaDAQ, sequence::Sequence) allAmps[name] = amps allPhases[name] = phases end - + @debug "prepareTx: Outputting the following amplitudes and phases:" allAmps allPhases setTxParams(daq, allAmps, allPhases, allAwg) end diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index 614b102a..86364293 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -75,8 +75,17 @@ function ControlSequence(txCont::TxDAQController, target::Sequence, daq::Abstrac for channel in periodicChannel otherComp = filter(!in(periodicElectricalComponents(channel)), periodicComponents) for comp in periodicElectricalComponents(channel) - # TODO smarter init value, maybe min between 1/10 and target (<- target might not be V) - amplitude!(comp, simpleChannel[channel].limitPeak/10) + # TODO: maybe remove code duplication with prepareTX, using T here would work, but then the TXDAQController does not know the V amplitude that was sent + amp = amplitude(comp) + pha = phase(comp) + if dimension(amp) == dimension(1.0u"T") + f_comp = ustrip(u"Hz", _baseFrequency) / divider(comp) + complex_comp = (amp*exp(im*pha)) * calibration(daq, id(channel))(f_comp) + amplitude!(comp,uconvert(u"V",abs(complex_comp))) + phase!(comp,angle(complex_comp)u"rad") + else + error("The amplitude for a channel that is controlled by a TxDAQController needs to be given in T") + end end if txCont.params.correctCrossCoupling for comp in otherComp @@ -329,6 +338,7 @@ function calcFieldsFromRef(cont::ControlSequence, uRef::Array{Float32, 4}) len = length(keys(cont.simpleChannel)) N = rxNumSamplingPoints(cont.currSequence) dividers = Int64[divider(components(channel)[1]) for channel in keys(cont.simpleChannel)] + frequencies = ustrip(u"Hz", txBaseFrequency(cont.currSequence)) ./ dividers Γ = zeros(ComplexF64, len, len, size(uRef, 3), size(uRef, 4)) sorted = uRef[:, cont.refIndices, :, :] @@ -338,7 +348,7 @@ function calcFieldsFromRef(cont::ControlSequence, uRef::Array{Float32, 4}) end end for d =1:len - c = ustrip(u"T/V", collect(Base.values(cont.simpleChannel))[d].feedback.calibration) + c = ustrip(u"T/V", collect(Base.values(cont.simpleChannel))[d].feedback.calibration(frequencies[d])) for e=1:len correction = c * -im * dividers[e]/dividers[d] * 2/N for j = 1:size(Γ, 3) @@ -355,10 +365,11 @@ function calcFieldFromRef(cont::ControlSequence, uRef, ::SortedRef) len = length(keys(cont.simpleChannel)) N = rxNumSamplingPoints(cont.currSequence) dividers = Int64[divider(components(channel)[1]) for channel in keys(cont.simpleChannel)] + frequencies = ustrip(u"Hz", txBaseFrequency(cont.currSequence)) ./ dividers Γ = zeros(ComplexF64, len, len) calcFieldFromRef!(Γ, cont, uRef, SortedRef()) for d =1:len - c = ustrip(u"T/V", collect(Base.values(cont.simpleChannel))[d].feedback.calibration) + c = ustrip(u"T/V", collect(Base.values(cont.simpleChannel))[d].feedback.calibration(frequencies[d])) for e=1:len correction = c * -im * dividers[e]/dividers[d] * 2/N Γ[d,e] = correction * Γ[d,e] @@ -381,7 +392,7 @@ end function calcDesiredField(cont::ControlSequence) seqChannel = keys(cont.simpleChannel) - temp = [ustrip(amplitude(components(ch)[1])) * exp(im*ustrip(phase(components(ch)[1]))) for ch in seqChannel] + temp = [ustrip(u"T",amplitude(components(ch)[1])) * exp(im*ustrip(u"rad",phase(components(ch)[1]))) for ch in seqChannel] return convert(Matrix{ComplexF64}, diagm(temp)) end @@ -488,13 +499,18 @@ function updateControlSequence!(cont::ControlSequence, newTx::Matrix) end function checkFieldToVolt(oldTx, Γ, cont::ControlSequence, txCont::TxDAQController) - calibFieldToVoltEstimate = [ustrip(u"V/T", ch.calibration) for ch in collect(Base.values(cont.simpleChannel))] - calibFieldToVoltMeasured = abs.(diag(oldTx) ./ diag(Γ)) - deviation = abs.(1.0 .- calibFieldToVoltMeasured./calibFieldToVoltEstimate) - @debug "We expected $(calibFieldToVoltEstimate) and got $(calibFieldToVoltMeasured), deviation: $deviation" - valid = maximum( deviation ) < txCont.params.fieldToVoltDeviation + dividers = Int64[divider(components(channel)[1]) for channel in keys(cont.simpleChannel)] + frequencies = ustrip(u"Hz", txBaseFrequency(cont.currSequence)) ./ dividers + calibFieldToVoltEstimate = [ustrip(u"V/T", collect(Base.values(cont.simpleChannel))[idx].calibration(frequencies[idx])) for idx in 1:length(keys(cont.simpleChannel))] + calibFieldToVoltMeasured = (diag(oldTx) ./ diag(Γ)) + abs_deviation = 1.0 .- abs.(calibFieldToVoltMeasured./calibFieldToVoltEstimate) + phase_deviation = angle.(calibFieldToVoltMeasured./calibFieldToVoltEstimate) + @debug "We expected $(calibFieldToVoltEstimate) and got $(calibFieldToVoltMeasured), deviation: $abs_deviation" + valid = maximum( abs_deviation ) < txCont.params.fieldToVoltDeviation if !valid - @warn "Measured field to volt deviates by $deviation from estimate, exceeding allowed deviation" + @warn "Measured field to volt deviates by $abs_deviation from estimate, exceeding allowed deviation" + elseif maximum(abs.(phase_deviation)) > 10/180*pi + @warn "The phase of the measured field to volt deviates by $phase_deviation from estimate. Continuing anyways..." end return valid end From 1ed58110ce997c490d4543b4bf6442b9a99bd3ad Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Wed, 5 Jul 2023 17:27:31 +0200 Subject: [PATCH 005/168] fix phase inconsistency --- src/Devices/DAQ/RedPitayaDAQ.jl | 2 +- src/Devices/Virtual/TxDAQController.jl | 4 ++-- src/Sequences/ContinuousElectricalChannel.jl | 14 +++++++++++++- src/Sequences/PeriodicElectricalChannel.jl | 17 +++++++++++++++-- 4 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index bdec9158..9a8598d6 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -676,7 +676,7 @@ function prepareTx(daq::RedPitayaDAQ, sequence::Sequence) f_comp = ustrip(u"Hz", txBaseFrequency(sequence)) / divider(comp) complex_comp = (amp*exp(im*pha)) * calibration(daq, name)(f_comp) amp = abs(complex_comp) - pha = angle(complex_comp) + pha = angle(complex_comp)u"rad" end push!(amps, amp) push!(phases, pha) diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index 86364293..5ee994ba 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -350,7 +350,7 @@ function calcFieldsFromRef(cont::ControlSequence, uRef::Array{Float32, 4}) for d =1:len c = ustrip(u"T/V", collect(Base.values(cont.simpleChannel))[d].feedback.calibration(frequencies[d])) for e=1:len - correction = c * -im * dividers[e]/dividers[d] * 2/N + correction = c * dividers[e]/dividers[d] * 2/N for j = 1:size(Γ, 3) for i = 1:size(Γ, 4) Γ[d, e, j, i] = correction * Γ[d, e, j, i] @@ -371,7 +371,7 @@ function calcFieldFromRef(cont::ControlSequence, uRef, ::SortedRef) for d =1:len c = ustrip(u"T/V", collect(Base.values(cont.simpleChannel))[d].feedback.calibration(frequencies[d])) for e=1:len - correction = c * -im * dividers[e]/dividers[d] * 2/N + correction = c * dividers[e]/dividers[d] * 2/N Γ[d,e] = correction * Γ[d,e] end end diff --git a/src/Sequences/ContinuousElectricalChannel.jl b/src/Sequences/ContinuousElectricalChannel.jl index e7f0ad36..9776c7f0 100644 --- a/src/Sequences/ContinuousElectricalChannel.jl +++ b/src/Sequences/ContinuousElectricalChannel.jl @@ -51,7 +51,19 @@ function createFieldChannel(channelID::AbstractString, channelType::Type{Continu end if haskey(channelDict, "phase") - phase = uparse.(channelDict["phase"]) + phaseDict = Dict("cosine"=>0.0u"rad", "cos"=>0.0u"rad","sine"=>pi/2u"rad", "sin"=>pi/2u"rad","-cosine"=>pi*u"rad", "-cos"=>pi*u"rad","-sine"=>-pi/2u"rad", "-sin"=>-pi/2u"rad") + phase = [] + for x in channelDict["phase"] + try + push!(phase, uparse.(x)) + catch + if haskey(phaseDict, x) + push!(phase, phaseDict[x]) + else + error("The value $x for the phase could not be parsed. Use either a unitful value, or one of the predefined keywords ($(keys(phaseDict)))") + end + end + end else phase = 0.0u"rad" # Default phase end diff --git a/src/Sequences/PeriodicElectricalChannel.jl b/src/Sequences/PeriodicElectricalChannel.jl index 3c8e4fe6..d3fb581a 100644 --- a/src/Sequences/PeriodicElectricalChannel.jl +++ b/src/Sequences/PeriodicElectricalChannel.jl @@ -108,7 +108,19 @@ function createChannelComponent(componentID::AbstractString, ::Type{PeriodicElec end if haskey(componentDict, "phase") - phase = uparse.(componentDict["phase"]) + phaseDict = Dict("sine"=>0.0u"rad", "sin"=>0.0u"rad","cosine"=>pi/2u"rad", "cos"=>pi/2u"rad","-sine"=>pi*u"rad", "-sin"=>pi*u"rad","-cosine"=>-pi/2u"rad", "-cos"=>-pi/2u"rad") + phase = [] + for x in componentDict["phase"] + try + push!(phase, uparse.(x)) + catch + if haskey(phaseDict, x) + push!(phase, phaseDict[x]) + else + error("The value $x for the phase could not be parsed. Use either a unitful value, or one of the predefined keywords ($(keys(phaseDict)))") + end + end + end else phase = fill(0.0u"rad", length(divider)) # Default phase end @@ -151,8 +163,9 @@ cycleDuration(channel::PeriodicElectricalChannel, baseFrequency::typeof(1.0u"Hz" isDfChannel(channel::PeriodicElectricalChannel) = channel.isDfChannel -export divider +export divider, divider! divider(component::ElectricalComponent, trigger::Integer=1) = length(component.divider) == 1 ? component.divider[1] : component.divider[trigger] +divider!(component::PeriodicElectricalComponent,value::Integer) = component.divider = value export amplitude, amplitude! amplitude(component::PeriodicElectricalComponent; period::Integer=1) = component.amplitude[period] From 5ab6f26c6aa2a432365b56ab96e7faee49d51444 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Wed, 5 Jul 2023 17:27:44 +0200 Subject: [PATCH 006/168] added divider! function --- src/Sequences/Sequence.jl | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Sequences/Sequence.jl b/src/Sequences/Sequence.jl index 3e0a9814..8b8a90d1 100644 --- a/src/Sequences/Sequence.jl +++ b/src/Sequences/Sequence.jl @@ -242,6 +242,17 @@ function phase!(channel::PeriodicElectricalChannel, componentId::AbstractString, end end +export divider! +function divider!(channel::PeriodicElectricalChannel, componentId::AbstractString, value::Integer) + index = findfirst(x -> id(x) == componentId, channel.components) + if !isnothing(index) + divider!(channel.components[index], value) + else + throw(ArgumentError("Channel $(id(channel)) has no component with id $componentid")) + end +end + + export acqGradient acqGradient(sequence::Sequence) = nothing # TODO: Implement From 066981068b5678ea06981c778dc565f7db7efbe5 Mon Sep 17 00:00:00 2001 From: Justin Ackers Date: Fri, 14 Jul 2023 16:20:12 +0200 Subject: [PATCH 007/168] added function for number of controlled channels --- src/Devices/Virtual/TxDAQController.jl | 28 ++++++++++--------- .../RobotBasedSystemMatrixProtocol.jl | 2 +- src/Protocols/Storage/ChainableBuffer.jl | 3 +- src/Protocols/Storage/ProducerConsumer.jl | 3 +- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index 5ee994ba..ed1af3b6 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -22,7 +22,7 @@ mutable struct ControlSequence targetSequence::Sequence currSequence::Sequence # Periodic Electric Components - simpleChannel::OrderedDict{PeriodicElectricalChannel, TxChannelParams} + controlledChannelsDict::OrderedDict{PeriodicElectricalChannel, TxChannelParams} sinLUT::Union{Matrix{Float64}, Nothing} cosLUT::Union{Matrix{Float64}, Nothing} refIndices::Vector{Int64} @@ -200,7 +200,7 @@ function controlTx(txCont::TxDAQController, seq::Sequence, control::ControlSeque # Hacky solution controlPhaseDone = false i = 1 - len = length(keys(control.simpleChannel)) + len = numControlledChannels(control) try while !controlPhaseDone && i <= txCont.params.maxControlSteps @info "CONTROL STEP $i" @@ -295,6 +295,8 @@ end getControlledChannel(::TxDAQController, seq::Sequence) = [channel for field in seq.fields if field.control for channel in field.channels if typeof(channel) <: PeriodicElectricalChannel && length(arbitraryElectricalComponents(channel)) == 0] getUncontrolledChannel(::TxDAQController, seq::Sequence) = [channel for field in seq.fields if !field.control for channel in field.channels if typeof(channel) <: PeriodicElectricalChannel] +numControlledChannels(cont::ControlSequence) = length(keys(cont.controlledChannelsDict)) + function createLUTs(seqChannel::Vector{PeriodicElectricalChannel}, seq::Sequence) N = rxNumSamplingPoints(seq) D = length(seqChannel) @@ -335,9 +337,9 @@ function calcFieldFromRef(cont::ControlSequence, uRef::Array{Float32, 3}, ::Unso end function calcFieldsFromRef(cont::ControlSequence, uRef::Array{Float32, 4}) - len = length(keys(cont.simpleChannel)) + len = numControlledChannels(cont) N = rxNumSamplingPoints(cont.currSequence) - dividers = Int64[divider(components(channel)[1]) for channel in keys(cont.simpleChannel)] + dividers = Int64[divider(components(channel)[1]) for channel in keys(cont.controlledChannelsDict)] frequencies = ustrip(u"Hz", txBaseFrequency(cont.currSequence)) ./ dividers Γ = zeros(ComplexF64, len, len, size(uRef, 3), size(uRef, 4)) @@ -348,7 +350,7 @@ function calcFieldsFromRef(cont::ControlSequence, uRef::Array{Float32, 4}) end end for d =1:len - c = ustrip(u"T/V", collect(Base.values(cont.simpleChannel))[d].feedback.calibration(frequencies[d])) + c = ustrip(u"T/V", collect(Base.values(cont.controlledChannelsDict))[d].feedback.calibration(frequencies[d])) for e=1:len correction = c * dividers[e]/dividers[d] * 2/N for j = 1:size(Γ, 3) @@ -362,14 +364,14 @@ function calcFieldsFromRef(cont::ControlSequence, uRef::Array{Float32, 4}) end function calcFieldFromRef(cont::ControlSequence, uRef, ::SortedRef) - len = length(keys(cont.simpleChannel)) + len = numControlledChannels(cont) N = rxNumSamplingPoints(cont.currSequence) - dividers = Int64[divider(components(channel)[1]) for channel in keys(cont.simpleChannel)] + dividers = Int64[divider(components(channel)[1]) for channel in keys(cont.controlledChannelsDict)] frequencies = ustrip(u"Hz", txBaseFrequency(cont.currSequence)) ./ dividers Γ = zeros(ComplexF64, len, len) calcFieldFromRef!(Γ, cont, uRef, SortedRef()) for d =1:len - c = ustrip(u"T/V", collect(Base.values(cont.simpleChannel))[d].feedback.calibration(frequencies[d])) + c = ustrip(u"T/V", collect(Base.values(cont.controlledChannelsDict))[d].feedback.calibration(frequencies[d])) for e=1:len correction = c * dividers[e]/dividers[d] * 2/N Γ[d,e] = correction * Γ[d,e] @@ -391,7 +393,7 @@ function calcFieldFromRef!(Γ::AbstractArray{ComplexF64, 2}, cont::ControlSequen end function calcDesiredField(cont::ControlSequence) - seqChannel = keys(cont.simpleChannel) + seqChannel = keys(cont.controlledChannelsDict) temp = [ustrip(u"T",amplitude(components(ch)[1])) * exp(im*ustrip(u"rad",phase(components(ch)[1]))) for ch in seqChannel] return convert(Matrix{ComplexF64}, diagm(temp)) end @@ -444,7 +446,7 @@ end function calcControlMatrix(cont::ControlSequence) # TxDAQController only works on one field atm (possible future TODO: update to multiple fields, matrix per field) field = fields(cont.currSequence)[1] - len = length(keys(cont.simpleChannel)) + len = numControlledChannels(cont) κ = zeros(ComplexF64, len, len) # In each channel the first component is the channels "own" component, the following are the ordered correction components of the other channel # -> For Channel 2 its components in the matrix row should be c2 c1 c3 for a 3x3 matrix @@ -499,9 +501,9 @@ function updateControlSequence!(cont::ControlSequence, newTx::Matrix) end function checkFieldToVolt(oldTx, Γ, cont::ControlSequence, txCont::TxDAQController) - dividers = Int64[divider(components(channel)[1]) for channel in keys(cont.simpleChannel)] + dividers = Int64[divider(components(channel)[1]) for channel in keys(cont.controlledChannelsDict)] frequencies = ustrip(u"Hz", txBaseFrequency(cont.currSequence)) ./ dividers - calibFieldToVoltEstimate = [ustrip(u"V/T", collect(Base.values(cont.simpleChannel))[idx].calibration(frequencies[idx])) for idx in 1:length(keys(cont.simpleChannel))] + calibFieldToVoltEstimate = [ustrip(u"V/T", collect(Base.values(cont.controlledChannelsDict))[idx].calibration(frequencies[idx])) for idx in 1:numControlledChannels(cont)] calibFieldToVoltMeasured = (diag(oldTx) ./ diag(Γ)) abs_deviation = 1.0 .- abs.(calibFieldToVoltMeasured./calibFieldToVoltEstimate) phase_deviation = angle.(calibFieldToVoltMeasured./calibFieldToVoltEstimate) @@ -519,7 +521,7 @@ function checkVoltLimits(newTx, cont::ControlSequence, txCont::TxDAQController) validChannel = zeros(Bool, size(newTx, 2)) for i = 1:size(newTx, 2) max = sum(abs.(newTx[i, :])) - validChannel[i] = max < ustrip(u"V", collect(Base.values(cont.simpleChannel))[i].limitPeak) + validChannel[i] = max < ustrip(u"V", collect(Base.values(cont.controlledChannelsDict))[i].limitPeak) end valid = all(validChannel) if !valid diff --git a/src/Protocols/RobotBasedSystemMatrixProtocol.jl b/src/Protocols/RobotBasedSystemMatrixProtocol.jl index 4cb1c32f..50d8475c 100644 --- a/src/Protocols/RobotBasedSystemMatrixProtocol.jl +++ b/src/Protocols/RobotBasedSystemMatrixProtocol.jl @@ -260,7 +260,7 @@ function prepareDAQ(protocol::RobotBasedSystemMatrixProtocol) protocol.restored = false end if isempty(protocol.systemMeasState.drivefield) - len = length(keys(protocol.contSequence.simpleChannel)) + len = numControlledChannels(protocol.contSequence) drivefield = zeros(ComplexF64, len, len, size(calib.signals, 3), size(calib.signals, 4)) calib.drivefield = mmap!(protocol, "observedField.bin", drivefield) applied = zeros(ComplexF64, len, len, size(calib.signals, 3), size(calib.signals, 4)) diff --git a/src/Protocols/Storage/ChainableBuffer.jl b/src/Protocols/Storage/ChainableBuffer.jl index 9a671756..beffd321 100644 --- a/src/Protocols/Storage/ChainableBuffer.jl +++ b/src/Protocols/Storage/ChainableBuffer.jl @@ -148,8 +148,7 @@ end function TxDAQControllerBuffer(tx::TxDAQController, sequence::ControlSequence) numFrames = acqNumFrames(sequence.targetSequence) numPeriods = acqNumPeriodsPerFrame(sequence.targetSequence) - # TODO function for length(keys(simpleChannel)) - len = length(keys(sequence.simpleChannel)) + len = numControlledChannels(sequence) buffer = zeros(ComplexF64, len, len, numPeriods, numFrames) return TxDAQControllerBuffer(1, buffer, tx) end diff --git a/src/Protocols/Storage/ProducerConsumer.jl b/src/Protocols/Storage/ProducerConsumer.jl index d0c6ea3c..3f87b1d5 100644 --- a/src/Protocols/Storage/ProducerConsumer.jl +++ b/src/Protocols/Storage/ProducerConsumer.jl @@ -2,8 +2,7 @@ SequenceMeasState(x, sequence::ControlSequence, sequenceBuffer::Nothing = nothin function SequenceMeasState(x, sequence::ControlSequence, sequenceBuffer::Vector{StorageBuffer}) numFrames = acqNumFrames(sequence.targetSequence) numPeriods = acqNumPeriodsPerFrame(sequence.targetSequence) - # TODO function for length(keys(simpleChannel)) - len = length(keys(sequence.simpleChannel)) + len = numControlledChannels(sequence) buffer = DriveFieldBuffer(1, zeros(ComplexF64, len, len, numPeriods, numFrames), sequence) avgFrames = acqNumFrameAverages(sequence.targetSequence) if avgFrames > 1 From 709302a4a427ad3ec46c9b83d2e34832b63adc0f Mon Sep 17 00:00:00 2001 From: Justin Ackers Date: Mon, 14 Aug 2023 10:12:20 +0200 Subject: [PATCH 008/168] added function to apply forward calib to sequence --- src/Devices/DAQ/DAQ.jl | 34 +++++++++++++++++++++++++++++++++ src/Devices/DAQ/RedPitayaDAQ.jl | 2 ++ 2 files changed, 36 insertions(+) diff --git a/src/Devices/DAQ/DAQ.jl b/src/Devices/DAQ/DAQ.jl index 84f5863b..25e3403e 100644 --- a/src/Devices/DAQ/DAQ.jl +++ b/src/Devices/DAQ/DAQ.jl @@ -226,6 +226,40 @@ function calibration(daq::AbstractDAQ, channelID::AbstractString) end +export applyForwardCalibration! +function applyForwardCalibration!(seq::Sequence, daq::AbstractDAQ) + + # TODO/JA: what about the other types of channels? + + for channel in periodicElectricalTxChannels(seq) + + offsetVolts = offset(channel)*calibration(daq, id(channel))(0) # use DC value for offsets + offset!(channel, offsetVolts) + + for comp in periodicElectricalComponents(channel) + amp = amplitude(comp) + pha = phase(comp) + if dimension(amp) != dimension(1.0u"V") + f_comp = ustrip(u"Hz", txBaseFrequency(sequence)) / divider(comp) + complex_comp = (amp*exp(im*pha)) * calibration(daq, id(channel))(f_comp) + amplitude!(comp,uconvert(u"V",abs(complex_comp))) + phase!(comp,angle(complex_comp)u"rad") + end + end + + for aw_comp in arbitraryElectricalComponents(channel) + wave = values(aw_comp) + if dimension(wave[1]) != dimension(1.0u"V") + f_comp = ustrip(u"Hz", txBaseFrequency(sequence)) / divider(comp) + f_awg = rfftfreq(2^14, f_comp*2^14) + wave = irfft(rfft(wave).*calibration(daq, name)(f_awg), 2^14) + values!(aw_comp, wave) + end + + end + end +end + #### Measurement Related Functions #### diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index 9a8598d6..26119d64 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -514,6 +514,8 @@ function setupTx(daq::RedPitayaDAQ, sequence::Sequence) error("The Red Pitaya DAQ cannot work with more than one period in a frame or frequency sweeps yet.") end + applyForwardCalibration!(sequence, daq) + # Iterate over sequence(!) channels execute!(daq.rpc) do batch baseFreq = txBaseFrequency(sequence) From a5c9d05c076b49b66376e8782e37ad9fa3c5e88f Mon Sep 17 00:00:00 2001 From: Justin Ackers Date: Mon, 14 Aug 2023 10:13:15 +0200 Subject: [PATCH 009/168] small updates to channels --- src/Sequences/ContinuousElectricalChannel.jl | 4 +- src/Sequences/PeriodicElectricalChannel.jl | 44 +++++++++++++------- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/src/Sequences/ContinuousElectricalChannel.jl b/src/Sequences/ContinuousElectricalChannel.jl index 9776c7f0..712c8589 100644 --- a/src/Sequences/ContinuousElectricalChannel.jl +++ b/src/Sequences/ContinuousElectricalChannel.jl @@ -13,7 +13,7 @@ Base.@kwdef struct ContinuousElectricalChannel <: AcyclicElectricalTxChannel # T "Phase of the component for each period of the field." phase::typeof(1.0u"rad") "Offset of the channel. If defined in Tesla, the calibration configured in the scanner will be used." - offset::Union{typeof(1.0u"T"), typeof(1.0u"A")} = 0.0u"T" + offset::Union{typeof(1.0u"T"), typeof(1.0u"V"), typeof(1.0u"A")} = 0.0u"T" "Waveform of the component." waveform::Waveform = WAVEFORM_SINE end @@ -51,7 +51,7 @@ function createFieldChannel(channelID::AbstractString, channelType::Type{Continu end if haskey(channelDict, "phase") - phaseDict = Dict("cosine"=>0.0u"rad", "cos"=>0.0u"rad","sine"=>pi/2u"rad", "sin"=>pi/2u"rad","-cosine"=>pi*u"rad", "-cos"=>pi*u"rad","-sine"=>-pi/2u"rad", "-sin"=>-pi/2u"rad") + phaseDict = Dict("sine"=>0.0u"rad", "sin"=>0.0u"rad","cosine"=>pi/2u"rad", "cos"=>pi/2u"rad","-sine"=>pi*u"rad", "-sin"=>pi*u"rad","-cosine"=>-pi/2u"rad", "-cos"=>-pi/2u"rad") phase = [] for x in channelDict["phase"] try diff --git a/src/Sequences/PeriodicElectricalChannel.jl b/src/Sequences/PeriodicElectricalChannel.jl index d3fb581a..c55697b9 100644 --- a/src/Sequences/PeriodicElectricalChannel.jl +++ b/src/Sequences/PeriodicElectricalChannel.jl @@ -31,6 +31,7 @@ Base.@kwdef mutable struct ArbitraryElectricalComponent <: ElectricalComponent id::AbstractString "Divider of the component." divider::Integer + "Values for the waveform of the component" values::Union{Vector{typeof(1.0u"T")}, Vector{typeof(1.0u"A")}, Vector{typeof(1.0u"V")}} end @@ -42,8 +43,9 @@ Base.@kwdef struct PeriodicElectricalChannel <: ElectricalTxChannel "Components added for this channel." components::Vector{ElectricalComponent} "Offset of the channel. If defined in Tesla, the calibration configured in the scanner will be used." - offset::Union{typeof(1.0u"T"), typeof(1.0u"V")} = 0.0u"T" + offset::Union{typeof(1.0u"T"), typeof(1.0u"A"), typeof(1.0u"V")} = 0.0u"T" isDfChannel::Bool = true + dcEnabled::Bool = true end channeltype(::Type{<:PeriodicElectricalChannel}) = ContinuousTxChannel() @@ -56,10 +58,12 @@ function createFieldChannel(channelID::AbstractString, ::Type{PeriodicElectrical tmp = uparse.(channelDict["offset"]) if eltype(tmp) <: Unitful.Current tmp = tmp .|> u"A" + elseif eltype(tmp) <: Unitful.Voltage + tmp = tmp .|> u"V" elseif eltype(tmp) <: Unitful.BField tmp = tmp .|> u"T" else - error("The value for an offset has to be either given as a current or in tesla. You supplied the type `$(eltype(tmp))`.") + error("The value for an offset has to be either given as a voltage, current or in tesla. You supplied the type `$(eltype(tmp))`.") end splattingDict[:offset] = tmp end @@ -67,6 +71,9 @@ function createFieldChannel(channelID::AbstractString, ::Type{PeriodicElectrical if haskey(channelDict, "isDfChannel") splattingDict[:isDfChannel] = channelDict["isDfChannel"] end + if haskey(channelDict, "dcEnabled") + splattingDict[:isDfChannel] = channelDict["dcEnabled"] + end components = Vector{ElectricalComponent}() componentsDict = [(k, v) for (k, v) in channelDict if v isa Dict] @@ -148,8 +155,9 @@ function createChannelComponent(componentID::AbstractString, ::Type{ArbitraryEle return ArbitraryElectricalComponent(id=componentID, divider=divider, values=values) end -export offset +export offset, offset! offset(channel::PeriodicElectricalChannel) = channel.offset +offset!(channel::PeriodicElectricalChannel, offset::Union{typeof(1.0u"T"),typeof(1.0u"V")}) = channel.offset = offset export components components(channel::PeriodicElectricalChannel) = channel.components @@ -163,6 +171,16 @@ cycleDuration(channel::PeriodicElectricalChannel, baseFrequency::typeof(1.0u"Hz" isDfChannel(channel::PeriodicElectricalChannel) = channel.isDfChannel +# TODO/JA: check if this can automatically be implemented for all setters (and getters) +function waveform!(channel::PeriodicElectricalChannel, componentId::AbstractString, value) + index = findfirst(x -> id(x) == componentId, channel.components) + if !isnothing(index) + waveform!(channel.components[index], value) + else + throw(ArgumentError("Channel $(id(channel)) has no component with id $componentid")) + end +end + export divider, divider! divider(component::ElectricalComponent, trigger::Integer=1) = length(component.divider) == 1 ? component.divider[1] : component.divider[trigger] divider!(component::PeriodicElectricalComponent,value::Integer) = component.divider = value @@ -178,30 +196,24 @@ function amplitude!(component::PeriodicElectricalComponent, value::Union{typeof( end amplitude(component::SweepElectricalComponent; trigger::Integer=1) = component.amplitude[period] amplitude(component::ArbitraryElectricalComponent) = maximum(abs.(component.values)) +amplitude!(component::ArbitraryElectricalComponent, values) = error("Can not change the amplitude of an ArbitraryElectricalComponent. Use values!() to change the waveform.") export phase, phase! phase(component::PeriodicElectricalComponent, trigger::Integer=1) = component.phase[trigger] phase!(component::PeriodicElectricalComponent, value::typeof(1.0u"rad"); period::Integer=1) = component.phase[period] = value phase(component::SweepElectricalComponent, trigger::Integer=1) = 0.0u"rad" -phase!(component::ArbitraryElectricalComponent, value::typeof(1.0u"rad"); period::Integer=1) = component.phase[period] = value -phase(component::ArbitraryElectricalComponent, trigger::Integer=1) = 0.0u"rad" #component.phase[period] +phase!(component::ArbitraryElectricalComponent, value::typeof(1.0u"rad"); period::Integer=1) = error("Can not change the phase of an ArbitraryElectricalComponent. Use values!() to change the waveform.") +phase(component::ArbitraryElectricalComponent, trigger::Integer=1) = 0.0u"rad" +export values, values! +values(component::ArbitraryElectricalComponent) = component.values +values!(component::ArbitraryElectricalComponent, values::Union{Vector{typeof(1.0u"T")}, Vector{typeof(1.0u"A")}, Vector{typeof(1.0u"V")}}) = component.values = values export waveform, waveform! waveform(component::ElectricalComponent) = component.waveform waveform!(component::ElectricalComponent, value) = component.waveform = value waveform(::ArbitraryElectricalComponent) = WAVEFORM_ARBITRARY - -values(component::ArbitraryElectricalComponent) = component.values - -function waveform!(channel::PeriodicElectricalChannel, componentId::AbstractString, value) - index = findfirst(x -> id(x) == componentId, channel.components) - if !isnothing(index) - waveform!(channel.components[index], value) - else - throw(ArgumentError("Channel $(id(channel)) has no component with id $componentid")) - end -end +waveform!(::ArbitraryElectricalComponent, value) = error("Can not change the waveform type of an ArbitraryElectricalComponent. Use values!() to change the waveform.") export id id(component::PeriodicElectricalComponent) = component.id From 98889ed877a27d312b7862c270c677af18ba3641 Mon Sep 17 00:00:00 2001 From: Justin Ackers Date: Mon, 14 Aug 2023 10:15:13 +0200 Subject: [PATCH 010/168] update component setup sequence should now set all unused components to 0, forward calibration is now applied at the top level in setupTx --- src/Devices/DAQ/RedPitayaDAQ.jl | 59 +++++++++++++++------------------ 1 file changed, 27 insertions(+), 32 deletions(-) diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index 26119d64..a47433e8 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -515,7 +515,7 @@ function setupTx(daq::RedPitayaDAQ, sequence::Sequence) end applyForwardCalibration!(sequence, daq) - + # Iterate over sequence(!) channels execute!(daq.rpc) do batch baseFreq = txBaseFrequency(sequence) @@ -531,11 +531,19 @@ function setupTxChannel(daq::RedPitayaDAQ, channel::PeriodicElectricalChannel, b end function setupTxChannel!(batch::ScpiBatch, daq::RedPitayaDAQ, channel::PeriodicElectricalChannel, baseFreq) channelIdx_ = channelIdx(daq, id(channel)) # Get index from scanner(!) channel - offsetVolts = offset(channel)*calibration(daq, id(channel))(0) # use DC value for offsets - @add_batch batch offsetDAC!(daq.rpc, channelIdx_, ustrip(u"V", offsetVolts)) - for (idx, component) in enumerate(components(channel)) + @add_batch batch offsetDAC!(daq.rpc, channelIdx_, ustrip(u"V", offset(channel))) + + for (idx, component) in enumerate(periodicElectricalComponents(channel)) # in the future add the SweepElectricalComponents as well setupTxComponent!(batch, daq, channel, component, idx, baseFreq) end + + awgComps = arbitraryElectricalComponents(channel) + if length(awgComps) > 1 + throw(SequenceConfigurationError("The channel with the ID $(id(channel)) defines more than one arbitrary electrical component, which is not supported.")) + elseif length(awgComps) == 1 + setupTxComponent!(batch, daq, channel, awgComps[1], 0, baseFreq) + end + end function setupTxComponent(daq::RedPitayaDAQ, channel::PeriodicElectricalChannel, component, componentIdx, baseFreq) @@ -545,7 +553,7 @@ function setupTxComponent(daq::RedPitayaDAQ, channel::PeriodicElectricalChannel, end function setupTxComponent!(batch::ScpiBatch, daq::RedPitayaDAQ, channel::PeriodicElectricalChannel, component::Union{PeriodicElectricalComponent, SweepElectricalComponent}, componentIdx, baseFreq) if componentIdx > 3 - throw(SequenceConfigurationError("The channel with the ID `$(id(channel))` defines more than three fixed waveform components, this is more than what the RedPitaya offers. Use an arbitrary waveform is a fourth is necessary.")) + throw(SequenceConfigurationError("The channel with the ID `$(id(channel))` defines more than three fixed waveform components, this is more than what the RedPitaya offers. Use an arbitrary waveform if a fourth is necessary.")) end channelIdx_ = channelIdx(daq, id(channel)) # Get index from scanner(!) channel freq = ustrip(u"Hz", baseFreq) / divider(component) @@ -665,41 +673,28 @@ function prepareTx(daq::RedPitayaDAQ, sequence::Sequence) allAmps = Dict{String, Vector{typeof(1.0u"V")}}() allPhases = Dict{String, Vector{typeof(1.0u"rad")}}() allAwg = Dict{String, Vector{typeof(1.0u"V")}}() - for channel in periodicElectricalTxChannels(sequence) + for channel in periodicElectricalTxChannels(sequence) name = id(channel) - amps = [] - phases = [] - wave = nothing - for comp in periodicElectricalComponents(channel) - # Lengths check == 1 happens in setupTx already - amp = amplitude(comp) - pha = phase(comp) - if dimension(amp) != dimension(1.0u"V") - f_comp = ustrip(u"Hz", txBaseFrequency(sequence)) / divider(comp) - complex_comp = (amp*exp(im*pha)) * calibration(daq, name)(f_comp) - amp = abs(complex_comp) - pha = angle(complex_comp)u"rad" - end - push!(amps, amp) - push!(phases, pha) + # This should set all unused components to zero, but what about unused channels? + amps = zeros(typeof(1.0u"V"), 3) + phases = zeros(typeof(1.0u"rad"), 3) + wave = zeros(typeof(1.0u"V"), 2^14) + for (i,comp) in enumerate(periodicElectricalComponents(channel)) + # Length check < 3 happens in setupTx already + amps[i] = amplitude(comp) + phases[i] = phase(comp) end + # Length check < 1 happens in setupTx already comps = arbitraryElectricalComponents(channel) - if length(comps) > 2 - throw(ScannerConfigurationError("Channel $(id(channel)) defines more than one arbitrary electrical component, which is not supported.")) - elseif length(comps) == 1 + if length(comps)==1 wave = values(comps[1]) - if dimension(wave[1]) != dimension(1.0u"V") # TODO/JA: make sure that the controller always uses V as its component waveform - f_comp = ustrip(u"Hz", txBaseFrequency(sequence)) / divider(comp) - f_awg = rfftfreq(2^14, f_comp*2^14) - wave = irfft(rfft(wave).*calibration(daq, name)(f_awg), 2^14) - end - allAwg[name] = wave end + allAwg[name] = wave allAmps[name] = amps allPhases[name] = phases end - @debug "prepareTx: Outputting the following amplitudes and phases:" allAmps allPhases + @debug "prepareTx: Outputting the following amplitudes and phases:" allAmps allPhases allAwg setTxParams(daq, allAmps, allPhases, allAwg) end @@ -827,7 +822,7 @@ numTxChannelsActive(daq::RedPitayaDAQ) = numChan(daq.rpc) #TODO: Currently, all numRxChannelsActive(daq::RedPitayaDAQ) = numRxChannelsReference(daq)+numRxChannelsMeasurement(daq) numRxChannelsReference(daq::RedPitayaDAQ) = length(daq.refChanIDs) numRxChannelsMeasurement(daq::RedPitayaDAQ) = length(daq.rxChanIDs) -numComponentsMax(daq::RedPitayaDAQ) = 3 +numComponentsMax(daq::RedPitayaDAQ) = 4 canPostpone(daq::RedPitayaDAQ) = true canConvolute(daq::RedPitayaDAQ) = false From c50c7d8b0b468146b0a43b91c38207555dd6e6e8 Mon Sep 17 00:00:00 2001 From: Justin Ackers Date: Mon, 14 Aug 2023 10:15:49 +0200 Subject: [PATCH 011/168] WIP: first untested version of new controller --- src/Devices/Virtual/TxDAQController.jl | 699 ++++++++++++------ .../RobotBasedSystemMatrixProtocol.jl | 6 +- src/Protocols/Storage/ChainableBuffer.jl | 4 +- src/Protocols/Storage/ProducerConsumer.jl | 4 +- 4 files changed, 493 insertions(+), 220 deletions(-) diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index ed1af3b6..374922c5 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -7,18 +7,16 @@ Base.@kwdef mutable struct TxDAQControllerParams <: DeviceParams maxControlSteps::Int64 = 20 fieldToVoltDeviation::Float64 = 0.2 correctCrossCoupling::Bool = false + controlDC::Bool = false end TxDAQControllerParams(dict::Dict) = params_from_dict(TxDAQControllerParams, dict) struct SortedRef end struct UnsortedRef end -struct ControlledChannel - seqChannel::PeriodicElectricalChannel - daqChannel::TxChannelParams -end +abstract type ControlSequence end -mutable struct ControlSequence +mutable struct CrossCouplingControlSequence <: ControlSequence targetSequence::Sequence currSequence::Sequence # Periodic Electric Components @@ -26,6 +24,15 @@ mutable struct ControlSequence sinLUT::Union{Matrix{Float64}, Nothing} cosLUT::Union{Matrix{Float64}, Nothing} refIndices::Vector{Int64} +end + +mutable struct AWControlSequence <: ControlSequence + targetSequence::Sequence + currSequence::Sequence + # Periodic Electric Components + controlledChannelsDict::OrderedDict{PeriodicElectricalChannel, TxChannelParams} + refIndices::Vector{Int64} + rfftIndices::BitArray{3} # Matrix of size length(controlledChannelsDict) x 4 (max. num of components) x len(rfft) # Arbitrary Waveform end @@ -42,90 +49,215 @@ function _init(tx::TxDAQController) # NOP end +function close(txCont::TxDAQController) + # NOP +end + neededDependencies(::TxDAQController) = [AbstractDAQ] optionalDependencies(::TxDAQController) = [SurveillanceUnit, Amplifier, TemperatureController] function ControlSequence(txCont::TxDAQController, target::Sequence, daq::AbstractDAQ) - # Prepare Periodic Electrical Components - seqControlledChannel = getControlledChannel(txCont, target) - simpleChannel = createPeriodicElectricalComponentDict(seqControlledChannel, target, daq) - sinLUT, cosLUT = createLUTs(seqControlledChannel, target) + if any(!isa.(getControlledChannels(target), PeriodicElectricalChannel)) + error("A field that requires control can only have PeriodicElectricalChannels.") # TODO/JA: check if this limitation can be lifted: would require special care when merging a sequence + end + + currSeq = prepareSequenceForControl(target) + applyForwardCalibration!(currSeq, daq) # uses the forward calibration to convert the values for the field from T to V + + seqControlledChannels = getControlledChannels(currSeq) + + # Dict(PeriodicElectricalChannel => TxChannelParams) + controlledChannelsDict = createControlledChannelsDict(seqControlledChannels, daq) # should this be changed to components instead of channels? + refIndices = createReferenceIndexMapping(controlledChannelsDict, daq) + + if txCont.correctCrossCoupling # use the old controller # TODO/JA: fix logic to decide which controller should be used + + if length(fields(currSeq)) > 1 + throw(IllegalStateException("Sequence requires control of multiple fields, which is currently not implemented with cross-coupling correction.")) + end + + # Check that we only control three channels, as our RedPitayaDAQs only have 3 signal components atm + if length(controlledChannelsDict) > 3 + throw(IllegalStateException("Sequence requires controlling of more than three channels, which is not possible with cross-coupling correction.")) + end + + # Check that channels only have one component + if any(x -> length(x.components) > 1, seqControlledChannels) + throw(IllegalStateException("Sequence has channel with more than one component. Such a channel cannot be controlled with cross-coupling correction")) + end + + if any(x -> isa(x.components, ArbitraryElectricalComponent), seqControlledChannels) + throw(IllegalStateException("Sequence has a channel with an ArbitraryElectricalComponent. Such a channel cannot be controlled with cross-coupling correction")) + end + + # temporarily remove first (and only) component from each channel + comps = [popfirst!(periodicElectricalComponents(channel)) for channel in periodicElectricalTxChannels(fields(currSeq)[1])] + + # insert all components into each channel in the same order, all comps from the other channels are set to 0T + for (i, channel) in enumerate(periodicElectricalTxChannels(fields(currSeq)[1])) + for (j, comp) in enumerate(comps) + copy_ = deepcopy(comp) + if i!=j + amplitude!(copy_, 0.0u"T") + end + push!(channel.components, copy_) + end + end + + sinLUT, cosLUT = createLUTs(seqControlledChannels, currSeq) # TODO/JA: check if this makes a difference between target and currSeq, though it should not + + + return CrossCouplingControlSequence(target, currSeq, controlledChannelsDict, sinLUT, cosLUT, refIndices) + + else # use the new controller + + # AW offset gets ignored -> should be zero anyways + if any(x->mean.(values.(arbitraryElectricalComponents(x))).>1u"µT", seqControlledChannels) + error("The DC-component of arbitrary waveform components cannot be handled during control! Please remove any DC-offset from your waveform and use the offset parameter of the corresponding channel!") + end + + # all channels on the same ref must agree with their offset -> just choose one of the channels that has the dcEnabled Flag + + + rfftIndices = createRFFTindices(controlledChannelsDict, target) + + return AWControlSequence(target, currSeq, controlledChannelsDict, refIndices, rfftIndices) + end +end + + +""" +This function creates the correct indices into a channel-wise rfft of the reference channels for each channel in the controlledChannelsDict +""" +function createRFFTindices(controlledChannelsDict::OrderedDict{PeriodicElectricalChannel, TxChannelParams}, seq::Sequence, daq::AbstractDAQ) + # Goal: create a Vector of index masks for each component + # N for every single-frequency component that has N periods in the sequence + # every Mth sample for all arbitraryWaveform components + numControlledChannels = length(keys(controlledChannelsDict)) + rfftSize = div(lcm(dfDivider(seq)),2)+1 + index_mask = falses(numControlledChannels, numComponentsMax(daq)+1, rfftSize) + + refChannelIdx = [channelIdx(daq, ch.feedback.channelID) for ch in collect(Base.values(controlledChannelsDict))] + + + for (i, channel) in enumerate(keys(controlledChannelsDict)) + for (j, comp) in components(channel) + dfCyclesPerPeriod = lcm(dfDivider(seq))/divider(comp) + if isa(comp, PeriodicElectricalComponent) + index_mask[i,j,dfCyclesPerPeriod+1] = true + elseif isa(comp, ArbitraryElectricalComponent) + # the frequency samples have a spacing dfCyclesPerPeriod in the spectrum, only use a maximum number of 2^13+1 points, since the waveform has a buffer length of 2^14 (=> rfft 2^13+1) + index_mask[i,j,dfCyclesPerPeriod+1:dfCyclesPerPeriod:minimum(rfftSize, (2^13+1)*dfCyclesPerPeriod+1)] .= true + @info "Debug: createRFFTindices" divider(comp) sum(index_mask[i,j,:]) + end + end + index_mask[i,end,1] = ~any(index_mask[findall(x->x==refChannelIdx[i], refChannelIdx),end,1]) && channel.dcEnabled # use the first DC enabled channel going to each ref channel to control the DC value with + end + + # TODO/JA: test if this works + # Do a or on the mask of all different components + allComponentMask = .|(eachslice(index_mask, dims=2)...) + uniqueRefChIdx = unique(refChannelIdx) + combinedRefChannelMask = falses(length(uniqueRefChIdx), size(allComponentMask, 2)) + # if two controlled channels are on the same ref channel they should be combined, maybe there is a more elegant way to do this, but I cant figure it out + for i in 1:numControlledChannels + combinedRefChannelMask .|= allComponentMask[findfirst(x->x==refChannelIdx[i], uniqueRefChIdx),:] + end + # if the total number of trues is smaller than the full array, there is an overlap of different components in the same FFT bin somewhere + if sum(combinedRefChannelMask) < sum(index_mask) + throw(SequenceConfigurationError("The controller can not control two different components, that have the same frequency on the same reference channel! This might also include arbitrary waveform components overlapping with normal components in frequency space or multiple TX channels using the same reference channel!")) + end + + allOffsets = offset.(keys(controlledChannelsDict)) + for refCh in uniqueRefChIdx + if length(unique(allOffsets[findall(x->x==refCh, refChannelIdx)]))>1 + throw(SequenceConfigurationError("The offsets of all channels going to the same ref channel need to be identical!")) + end + end + + return index_mask +end + +function createLUTs(seqChannel::Vector{PeriodicElectricalChannel}, seq::Sequence) + N = rxNumSamplingPoints(seq) + D = length(seqChannel) + + dfCyclesPerPeriod = Int[lcm(dfDivider(seq))/divider(components(chan)[i]) for (i,chan) in enumerate(seqChan)] + + sinLUT = zeros(D,N) + cosLUT = zeros(D,N) + for d=1:D + for n=1:N + sinLUT[d,n] = sin(2 * pi * (n-1) * dfCyclesPerPeriod[d] / N) + cosLUT[d,n] = cos(2 * pi * (n-1) * dfCyclesPerPeriod[d] / N) + end + end + return sinLUT, cosLUT +end + +""" +Returns a vector of indices into the the DAQ data after the channels have already been selected by the daq.refChanIDs to select the correct reference channels in the order of the controlledChannels +""" +function createReferenceIndexMapping(controlledChannelsDict::OrderedDict{PeriodicElectricalChannel, TxChannelParams}, daq::AbstractDAQ) + # Dict(RedPitaya ChannelIndex => Index in daq.refChanIDs) + mapping = Dict( b => a for (a,b) in enumerate(channelIdx(daq, daq.refChanIDs))) + # RedPitaya ChannelIndex der Feedback-Kanäle in Reihenfolge der Kanäle im controlledChannelsDict + controlOrderChannelIndices = [channelIdx(daq, ch.feedback.channelID) for ch in collect(Base.values(controlledChannelsDict))] + # Index in daq.refChanIDs in der Reihenfolge der Kanäle im controlledChannelsDict + return [mapping[x] for x in controlOrderChannelIndices] +end - currSeq = target +function prepareSequenceForControl(seq::Sequence) + # Ausgangslage: Eine Sequenz enthält eine Reihe an Feldern, von denen ggf nicht alle geregelt werden sollen + # Jedes dieser Felder enthält eine Reihe an Channels, die nicht alle geregelt werden können (nur periodicElectricalChannel) + + # Ziel: Eine Sequenz, die, wenn Sie abgespielt wird, alle Informationen beinhaltet, die benötigt werden, um die Regelung durchzuführen, Sendefelder in V - _name = "Control Sequence for target $(name(target))" + _name = "Control Sequence for target $(name(seq))" description = "" - _targetScanner = targetScanner(target) - _baseFrequency = baseFrequency(target) + _targetScanner = targetScanner(seq) + _baseFrequency = baseFrequency(seq) general = GeneralSettings(;name=_name, description = description, targetScanner = _targetScanner, baseFrequency = _baseFrequency) - acq = AcquisitionSettings(;channels = RxChannel[], bandwidth = rxBandwidth(target)) + acq = AcquisitionSettings(;channels = RxChannel[], bandwidth = rxBandwidth(seq)) # uses the default values of 1 for numPeriodsPerFrame, numFrames, numAverages, numFrameAverages _fields = MagneticField[] - for field in fields(target) + for field in fields(seq) if control(field) _id = id(field) safeStart = safeStartInterval(field) safeTrans = safeTransitionInterval(field) safeEnd = safeEndInterval(field) safeError = safeErrorInterval(field) - # Init purely periodic electrical component channel - periodicChannel = [deepcopy(channel) for channel in periodicElectricalTxChannels(field) if length(arbitraryElectricalComponents(channel)) == 0] - periodicComponents = [comp for channel in periodicChannel for comp in periodicElectricalComponents(channel)] + # Use only periodic electrical channels + periodicChannel = [deepcopy(channel) for channel in periodicElectricalTxChannels(field)] + #periodicComponents = [comp for channel in periodicChannel for comp in periodicElectricalComponents(channel)] for channel in periodicChannel - otherComp = filter(!in(periodicElectricalComponents(channel)), periodicComponents) for comp in periodicElectricalComponents(channel) - # TODO: maybe remove code duplication with prepareTX, using T here would work, but then the TXDAQController does not know the V amplitude that was sent - amp = amplitude(comp) - pha = phase(comp) - if dimension(amp) == dimension(1.0u"T") - f_comp = ustrip(u"Hz", _baseFrequency) / divider(comp) - complex_comp = (amp*exp(im*pha)) * calibration(daq, id(channel))(f_comp) - amplitude!(comp,uconvert(u"V",abs(complex_comp))) - phase!(comp,angle(complex_comp)u"rad") - else - error("The amplitude for a channel that is controlled by a TxDAQController needs to be given in T") + if dimension(amplitude(comp)) != dimension(1.0u"T") + error("The amplitude components of a field that is controlled by a TxDAQController need to be given in T. Please fix component $(id(comp)) of channel $(id(channel))") end end - if txCont.params.correctCrossCoupling - for comp in otherComp - copy = deepcopy(comp) - amplitude!(copy, 0.0u"V") - push!(channel, copy) + for comp in arbitraryElectricalComponents(channel) + if dimension(values(comp)[1]) != dimension(1.0u"T") + error("The waveform components of a field that is controlled by a TxDAQController need to be given in T. Please fix component $(id(comp)) of channel $(id(channel))") end end end - # Init arbitrary waveform - # TODO Implement AWG contField = MagneticField(;id = _id, channels = periodicChannel, safeStartInterval = safeStart, safeTransitionInterval = safeTrans, safeEndInterval = safeEnd, safeErrorInterval = safeError, control = true) push!(_fields, contField) end end - - # Create Ref Indexing - mapping = Dict( b => a for (a,b) in enumerate(channelIdx(daq, daq.refChanIDs))) - controlOrderChannelIndices = [channelIdx(daq, ch.feedback.channelID) for ch in collect(Base.values(simpleChannel))] - refIndices = [mapping[x] for x in controlOrderChannelIndices] - - - currSeq = Sequence(;general = general, acquisition = acq, fields = _fields) - return ControlSequence(target, currSeq, simpleChannel, sinLUT, cosLUT, refIndices) + return Sequence(;general = general, acquisition = acq, fields = _fields) end -acyclicElectricalTxChannels(cont::ControlSequence) = acyclicElectricalTxChannels(cont.targetSequence) -periodicElectricalTxChannels(cont::ControlSequence) = periodicElectricalTxChannels(cont.targetSequence) -acqNumFrames(cont::ControlSequence) = acqNumFrames(cont.targetSequence) -acqNumFrameAverages(cont::ControlSequence) = acqNumFrameAverages(cont.targetSequence) -acqNumFrames(cont::ControlSequence, x) = acqNumFrames(cont.targetSequence, x) -acqNumFrameAverages(cont::ControlSequence, x) = acqNumFrameAverages(cont.targetSequence, x) - -function createPeriodicElectricalComponentDict(seqControlledChannel::Vector{PeriodicElectricalChannel}, seq::Sequence, daq::AbstractDAQ) +function createControlledChannelsDict(seqControlledChannels::Vector{PeriodicElectricalChannel}, daq::AbstractDAQ) missingControlDef = [] dict = OrderedDict{PeriodicElectricalChannel, TxChannelParams}() - for seqChannel in seqControlledChannel + for seqChannel in seqControlledChannels name = id(seqChannel) daqChannel = get(daq.params.channels, name, nothing) if isnothing(daqChannel) || isnothing(daqChannel.feedback) || !in(daqChannel.feedback.channelID, daq.refChanIDs) @@ -140,27 +272,22 @@ function createPeriodicElectricalComponentDict(seqControlledChannel::Vector{Peri throw(IllegalStateException(message)) end - # Check that we only control three channels, as our RedPitayaDAQs only have 3 signal components atm - if length(dict) > 3 - throw(IllegalStateException("Sequence requires controlling of more than four channels, which is currently not implemented.")) - end - - # Check that channels only have one component - if any(x -> length(x.components) > 1, seqControlledChannel) - throw(IllegalStateException("Sequence has channel with more than one component. Such a channel cannot be controlled by this controller")) - end return dict end +############################################################################### +############## Top-Level functions for interacting with the controller +############################################################################### + function controlTx(txCont::TxDAQController, seq::Sequence, ::Nothing = nothing) daq = dependency(txCont, AbstractDAQ) setupRx(daq, seq) - control = ControlSequence(txCont, seq, daq) + control = ControlSequence(txCont, seq, daq) # depending on the controlled channels and settings this will select the appropiate type of ControlSequence return controlTx(txCont, seq, control) end -function controlTx(txCont::TxDAQController, seq::Sequence, control::ControlSequence) +function controlTx(txCont::TxDAQController, control::ControlSequence) # Prepare and check channel under control daq = dependency(txCont, AbstractDAQ) @@ -190,7 +317,7 @@ function controlTx(txCont::TxDAQController, seq::Sequence, control::ControlSeque end if !isempty(amps) # Only enable amps that amplify a channel of the current sequence - txChannelIds = id.(vcat(acyclicElectricalTxChannels(seq), periodicElectricalTxChannels(seq))) + txChannelIds = id.(vcat(acyclicElectricalTxChannels(control.targetSequence), periodicElectricalTxChannels(control.targetSequence))) amps = filter(amp -> in(channelId(amp), txChannelIds), amps) @sync for amp in amps @async turnOn(amp) @@ -200,14 +327,23 @@ function controlTx(txCont::TxDAQController, seq::Sequence, control::ControlSeque # Hacky solution controlPhaseDone = false i = 1 - len = numControlledChannels(control) + try + if txCont.params.controlDC # is this only possible with AWControlSequence?? -> maybe do for all channels that are dcEnabled + # create sequence with every field off, dc set to zero, basically trigger off + # calculate mean for each ref channel + # create sequence with offsets set to -mean(ref)(V)*feedback.calibration(T/V)*forwardcalibration(V/T) + # optional: send sequence and calculate perfect zero with two points + # update redpitaya DAC calibration values + error("The DC offset control is not yet implemented") + end + while !controlPhaseDone && i <= txCont.params.maxControlSteps @info "CONTROL STEP $i" # Prepare control measurement setup(daq, control.currSequence) channel = Channel{channelType(daq)}(32) - buffer = AsyncBuffer(FrameSplitterBuffer(daq, StorageBuffer[DriveFieldBuffer(1, zeros(ComplexF64,len, len, 1, 1), control)]), daq) + buffer = AsyncBuffer(FrameSplitterBuffer(daq, StorageBuffer[DriveFieldBuffer(1, zeros(ComplexF64, controlMatrixShape(control)..., 1, 1), control)]), daq) @info "Control measurement started" producer = @async begin @debug "Starting control producer" @@ -228,9 +364,9 @@ function controlTx(txCont::TxDAQController, seq::Sequence, control::ControlSeque @info "Control measurement finished" @info "Evaluating control step" - uRef = read(sink(buffer, DriveFieldBuffer))[:, :, 1, 1] - if !isnothing(uRef) - controlPhaseDone = controlStep!(control, txCont, uRef, Ω) == UNCHANGED + Γ = read(sink(buffer, DriveFieldBuffer))[:, :, 1, 1] # calcFieldsFromRef happened here already + if !isnothing(Γ) + controlPhaseDone = controlStep!(control, txCont, Γ, Ω) == UNCHANGED if controlPhaseDone @info "Could control" else @@ -283,43 +419,71 @@ function controlTx(txCont::TxDAQController, seq::Sequence, control::ControlSeque return control end +""" +Returns a Sequence that is merged from the control result and all uncontrolled field of the given ControlSequence +""" +function getControlResult(cont::ControlSequence)::Sequence + + # Use the magnetic field that are controlled from currSeq and all uncontrolled fields and general settings from target -function setup(daq::RedPitayaDAQ, sequence::ControlSequence) - stopTx(daq) - setupRx(daq, sequence.targetSequence) - setupTx(daq, sequence.currSequence) - prepareTx(daq, sequence.currSequence) - setSequenceParams(daq, sequence.targetSequence) -end - -getControlledChannel(::TxDAQController, seq::Sequence) = [channel for field in seq.fields if field.control for channel in field.channels if typeof(channel) <: PeriodicElectricalChannel && length(arbitraryElectricalComponents(channel)) == 0] -getUncontrolledChannel(::TxDAQController, seq::Sequence) = [channel for field in seq.fields if !field.control for channel in field.channels if typeof(channel) <: PeriodicElectricalChannel] - -numControlledChannels(cont::ControlSequence) = length(keys(cont.controlledChannelsDict)) + _name = "Control Result for target $(name(cont.target))" + general = GeneralSettings(;name=_name, description = descrption(cont.target), targetScanner = targetScanner(cont.target), baseFrequency = baseFrequency(cont.target)) + acq = cont.target.acquisition -function createLUTs(seqChannel::Vector{PeriodicElectricalChannel}, seq::Sequence) - N = rxNumSamplingPoints(seq) - D = length(seqChannel) - cycle = ustrip(dfCycle(seq)) - base = ustrip(dfBaseFrequency(seq)) - dfFreq = [base/x.components[1].divider for x in seqChannel] - - sinLUT = zeros(D,N) - cosLUT = zeros(D,N) - for d=1:D - Y = round(Int64, cycle*dfFreq[d] ) - for n=1:N - sinLUT[d,n] = sin(2 * pi * (n-1) * Y / N) - cosLUT[d,n] = cos(2 * pi * (n-1) * Y / N) + _fields = MagneticField[] + for field in fields(cont.currSeq) + _id = id(field) + safeStart = safeStartInterval(field) + safeTrans = safeTransitionInterval(field) + safeEnd = safeEndInterval(field) + safeError = safeErrorInterval(field) + #TODO/JA: should the channels be a copy? Is it even necessary to create a new object just to set control to false? Maybe this will work anyways + contField = MagneticField(;id = _id, channels = deepcopy(channels(field)), safeStartInterval = safeStart, safeTransitionInterval = safeTrans, + safeEndInterval = safeEnd, safeErrorInterval = safeError, decouple=false, control = false) + push!(_fields, contField) + end + for field in fields(cont.target) + if !control(field) + push!(_fields, field) end end - return sinLUT, cosLUT + + return Sequence(;general = general, acquisition = acq, fields = _fields) end +setup(daq::AbstractDAQ, sequence::ControlSequence) = setup(daq, getControlResult(sequence)) + + +# TODO/JA: check if changes here needs changes somewhere else +getControlledFields(seq::Sequence) = [field for field in seq.fields if field.control] +getControlledChannels(seq::Sequence) = [channel for field in seq.fields if field.control for channel in field.channels] +# TODO/JA: is getControlledChannels(cont) always equal to getControlledChannels(cont.currSeq) ?? +getControlledChannels(cont::ControlSequence) = keys(cont.controlledChannelsDict) # maybe add collect here as well? for most uses this not needed +getControlledDAQChannels(cont::ControlSequence) = collect(Base.values(cont.contrlledChannelsDict)) +getPrimaryComponents(cont::CrossCouplingControlSequence) = [components(channel)[i] for (i,channel) in enumerate(getControlledChannels(cont))] + +acyclicElectricalTxChannels(cont::ControlSequence) = acyclicElectricalTxChannels(cont.targetSequence) +periodicElectricalTxChannels(cont::ControlSequence) = periodicElectricalTxChannels(cont.targetSequence) +acqNumFrames(cont::ControlSequence) = acqNumFrames(cont.targetSequence) +acqNumFrameAverages(cont::ControlSequence) = acqNumFrameAverages(cont.targetSequence) +acqNumFrames(cont::ControlSequence, x) = acqNumFrames(cont.targetSequence, x) +acqNumFrameAverages(cont::ControlSequence, x) = acqNumFrameAverages(cont.targetSequence, x) +numControlledChannels(cont::ControlSequence) = length(getControlledChannels(cont)) + +controlMatrixShape(cont::AWControlSequence) = (numControlledChannels(cont), size(cont.rfftIndices,3)) +controlMatrixShape(cont::CrossCouplingControlSequence) = (numControlledChannels(cont), numControlledChannels(cont)) + + +################################################################################# +########## Functions for the control steps +################################################################################# + + + controlStep!(cont::ControlSequence, txCont::TxDAQController, uRef) = controlStep!(cont, txCont, uRef, calcDesiredField(cont)) -controlStep!(cont::ControlSequence, txCont::TxDAQController, uRef, Ω::Matrix) = controlStep!(cont, txCont, calcFieldFromRef(cont, uRef), Ω) -function controlStep!(cont::ControlSequence, txCont::TxDAQController, Γ::Matrix, Ω::Matrix) - if checkFieldDeviation(Γ, Ω, txCont) +controlStep!(cont::ControlSequence, txCont::TxDAQController, uRef, Ω::Matrix{<:Complex}) = controlStep!(cont, txCont, calcFieldsFromRef(cont, uRef), Ω) +function controlStep!(cont::ControlSequence, txCont::TxDAQController, Γ::Matrix{<:Complex}, Ω::Matrix{<:Complex}) + if checkFieldDeviation(cont, txCont, Γ, Ω) return UNCHANGED elseif updateControl!(cont, txCont, Γ, Ω) return UPDATED @@ -328,18 +492,88 @@ function controlStep!(cont::ControlSequence, txCont::TxDAQController, Γ::Matrix end end -calcFieldFromRef(cont::ControlSequence, uRef; frame::Int64 = 1, period::Int64 = 1) = calcFieldFromRef(cont, uRef, UnsortedRef(), frame = frame, period = period) -function calcFieldFromRef(cont::ControlSequence, uRef::Array{Float32, 4}, ::UnsortedRef; frame::Int64 = 1, period::Int64 = 1) - return calcFieldFromRef(cont, uRef[:, :, :, frame], UnsortedRef(), period = period) +#checkFieldDeviation(cont::ControlSequence, txCont::TxDAQController, uRef) = checkFieldDeviation(cont, txCont, calcFieldFromRef(cont, uRef)) +checkFieldDeviation(cont::ControlSequence, txCont::TxDAQController, Γ::Matrix{<:Complex}) = checkFieldDeviation(cont, txCont, Γ, calcDesiredField(cont)) +function checkFieldDeviation(cont::ControlSequence, txCont::TxDAQController, Γ::Matrix{<:Complex}, Ω::Matrix{<:Complex}) + if correct_coupling || isa(cont, AWControlSequence) + diff = Ω - Γ + else + diff = diagm(diag(Ω)) - diagm(diag(Γ)) + end + deviation = maximum(abs.(diff)) / maximum(abs.(Ω)) + @debug "Check field deviation" Ω Γ + @debug "Ω - Γ = " diff + @info "deviation = $(deviation) allowed= $(txCont.params.amplitudeAccuracy)" + return deviation < txCont.params.amplitudeAccuracy +end + + +updateControl!(cont::ControlSequence, txCont::TxDAQController, uRef) = updateControl!(cont, txCont, calcFieldFromRef(cont, uRef), calcDesiredField(cont)) +function updateControl!(cont::ControlSequence, txCont::TxDAQController, Γ::Matrix{<:Complex}, Ω::Matrix{<:Complex}) + @debug "Updating control values" + κ = calcControlMatrix(cont) + newTx = updateControlMatrix(cont, txCont, Γ, Ω, κ) + if checkFieldToVolt(κ, Γ, cont, txCont) && checkVoltLimits(newTx, cont) + updateControlSequence!(cont, newTx) + return true + else + @warn "New control values are not allowed" + return false + end end -function calcFieldFromRef(cont::ControlSequence, uRef::Array{Float32, 3}, ::UnsortedRef; period::Int64 = 1) - return calcFieldFromRef(cont, view(uRef[:, cont.refIndices, :], :, :, period), SortedRef()) + +# Γ: Matrix from Ref +# Ω: Desired Matrix +# κ: Last Set Matrix +function updateControlMatrix(::CrossCouplingControlSequence, txCont::TxDAQController, Γ::Matrix{<:Complex}, Ω::Matrix{<:Complex}, κ::Matrix{<:Complex}) + if txCont.params.correctCrossCoupling + β = Γ*inv(κ) + else + β = diagm(diag(Γ))*inv(diagm(diag(κ))) + end + newTx = inv(β)*Ω + @debug "Last matrix:" κ + @debug "Ref matrix" Γ + @debug "Desired matrix" Ω + @debug "New matrix" newTx + return newTx end -function calcFieldsFromRef(cont::ControlSequence, uRef::Array{Float32, 4}) +function updateControlMatrix(cont::AWControlSequence, txCont::TxDAQController, Γ::Matrix{<:Complex}, Ω::Matrix{<:Complex}, κ::Matrix{<:Complex}) + # TODO/JA: can we divide by zero here accidentally? Will the fieldtoVolt check fail, if we include the components, that are not part of the signal + newTx = zeros(ComplexF64, controlMatrixShape(cont)) + # potentially blank out every component that we are not controlling? -> maybe not necessary, but could increase noise + β = Γ[:,2:end] / κ[:,2:end] + newTx[:,2:end] = inv(β) * Ω[:,2:end] + + # no coupling for DC! + newTx[:,1] = κ[:,1]./Γ[:,1].*Ω[:,1] + + @debug "Last matrix:" κ + @debug "Ref matrix" Γ + @debug "Desired matrix" Ω + @debug "New matrix" newTx + + return newTx +end + +################################################################################# +########## Functions for calculating the field matrix in T from the reference channels +################################################################################# + + +# calcFieldFromRef(cont::CrossCouplingControlSequence, uRef; frame::Int64 = 1, period::Int64 = 1) = calcFieldFromRef(cont, uRef, UnsortedRef(), frame = frame, period = period) +# function calcFieldFromRef(cont::CrossCouplingControlSequence, uRef::Array{Float32, 4}, ::UnsortedRef; frame::Int64 = 1, period::Int64 = 1) +# return calcFieldFromRef(cont, uRef[:, :, :, frame], UnsortedRef(), period = period) +# end +# function calcFieldFromRef(cont::CrossCouplingControlSequence, uRef::Array{Float32, 3}, ::UnsortedRef; period::Int64 = 1) +# return calcFieldFromRef(cont, view(uRef[:, cont.refIndices, :], :, :, period), SortedRef()) +# end + +function calcFieldsFromRef(cont::CrossCouplingControlSequence, uRef::Array{Float32, 4}) len = numControlledChannels(cont) N = rxNumSamplingPoints(cont.currSequence) - dividers = Int64[divider(components(channel)[1]) for channel in keys(cont.controlledChannelsDict)] + dividers = Int64[divider.(getPrimaryComponents(cont))] frequencies = ustrip(u"Hz", txBaseFrequency(cont.currSequence)) ./ dividers Γ = zeros(ComplexF64, len, len, size(uRef, 3), size(uRef, 4)) @@ -350,7 +584,7 @@ function calcFieldsFromRef(cont::ControlSequence, uRef::Array{Float32, 4}) end end for d =1:len - c = ustrip(u"T/V", collect(Base.values(cont.controlledChannelsDict))[d].feedback.calibration(frequencies[d])) + c = ustrip(u"T/V", getControlledDAQChannels(cont)[d].feedback.calibration(frequencies[d])) for e=1:len correction = c * dividers[e]/dividers[d] * 2/N for j = 1:size(Γ, 3) @@ -363,15 +597,25 @@ function calcFieldsFromRef(cont::ControlSequence, uRef::Array{Float32, 4}) return Γ end -function calcFieldFromRef(cont::ControlSequence, uRef, ::SortedRef) +function calcFieldsFromRef(cont::AWControlSequence, uRef::Array{Float32,4}) + N = rxNumSamplingPoints(cont.currSequence) + # do rfft channel wise and correct with the transfer function, return as (num control channels x len rfft x periods x frames) Matrix, the selection of [:,:,1,1] is done in controlTx + spectrum = rfft(uRef, 1)/0.5N + sortedSpectrum = permutedims(spectrum[:, cont.refIndices, :, :], (2,1,3,4)) + frequencies = rfftfreq(N, rxSamplingRate(cont.currSequence)) + fb_calibration = reduce(vcat, [ustrip.(u"T/V", chan.feedback.calibration(frequencies)) for chan in getControlledDAQChannels(cont)]') + return sortedSpectrum.*fb_calibration +end + +function calcFieldFromRef(cont::CrossCouplingControlSequence, uRef, ::SortedRef) len = numControlledChannels(cont) N = rxNumSamplingPoints(cont.currSequence) - dividers = Int64[divider(components(channel)[1]) for channel in keys(cont.controlledChannelsDict)] - frequencies = ustrip(u"Hz", txBaseFrequency(cont.currSequence)) ./ dividers + dividers = Int64[divider.(getPrimaryComponents(cont))] + frequencies = ustrip(u"Hz", txBaseFrequency(cont.currSequence)) ./ dividers Γ = zeros(ComplexF64, len, len) calcFieldFromRef!(Γ, cont, uRef, SortedRef()) for d =1:len - c = ustrip(u"T/V", collect(Base.values(cont.controlledChannelsDict))[d].feedback.calibration(frequencies[d])) + c = ustrip(u"T/V", getControlledDAQChannels(cont)[d].feedback.calibration(frequencies[d])) for e=1:len correction = c * dividers[e]/dividers[d] * 2/N Γ[d,e] = correction * Γ[d,e] @@ -380,7 +624,7 @@ function calcFieldFromRef(cont::ControlSequence, uRef, ::SortedRef) return Γ end -function calcFieldFromRef!(Γ::AbstractArray{ComplexF64, 2}, cont::ControlSequence, uRef, ::SortedRef) +function calcFieldFromRef!(Γ::AbstractArray{ComplexF64, 2}, cont::CrossCouplingControlSequence, uRef, ::SortedRef) len = size(Γ, 1) for d=1:len for e=1:len @@ -392,119 +636,118 @@ function calcFieldFromRef!(Γ::AbstractArray{ComplexF64, 2}, cont::ControlSequen return Γ end -function calcDesiredField(cont::ControlSequence) - seqChannel = keys(cont.controlledChannelsDict) - temp = [ustrip(u"T",amplitude(components(ch)[1])) * exp(im*ustrip(u"rad",phase(components(ch)[1]))) for ch in seqChannel] - return convert(Matrix{ComplexF64}, diagm(temp)) -end -checkFieldDeviation(uRef, cont::ControlSequence, txCont::TxDAQController) = checkFieldDeviation(calcFieldFromRef(cont, uRef), cont, txCont) -checkFieldDeviation(Γ::Matrix, cont::ControlSequence, txCont::TxDAQController) = checkFieldDeviation(Γ, calcDesiredField(cont), txCont) -function checkFieldDeviation(Γ::Matrix, Ω::Matrix, txCont::TxDAQController) - if txCont.params.correctCrossCoupling - diff = Ω - Γ - else - diff = diagm(diag(Ω)) - diagm(diag(Γ)) +################################################################################# +########## Functions for creating and applying the matrix representation +########## of the controller to the sequence(s) +################################################################################# + + +# Convert Target Sequence to Matrix in T + +function calcDesiredField(cont::CrossCouplingControlSequence) + desiredField = zeros(ComplexF64, controlMatrixShape(cont)) + for (i, channel) in enumerate(getControlledChannels(cont.targetSequence)) + comp = components(channel)[1] + desiredField[i, i] = ustrip(u"V", amplitude(comp)) * exp(im*ustrip(u"rad", phase(comp))) end - deviation = maximum(abs.(diff)) / maximum(abs.(Ω)) - @debug "Check field deviation" Ω Γ - @debug "Ω - Γ = " diff - @info "deviation = $(deviation) allowed= $(txCont.params.amplitudeAccuracy)" - return deviation < txCont.params.amplitudeAccuracy + return desiredField end -updateControl!(cont::ControlSequence, txCont::TxDAQController, uRef) = updateControl!(cont, txCont, calcFieldFromRef(cont, uRef), calcDesiredField(cont)) -function updateControl!(cont::ControlSequence, txCont::TxDAQController, Γ::Matrix, Ω::Matrix) - @debug "Updating control values" - κ = calcControlMatrix(cont) - newTx = updateControlMatrix(Γ, Ω, κ, correct_coupling = txCont.params.correctCrossCoupling) - if checkFieldToVolt(κ, Γ, cont, txCont) && checkVoltLimits(newTx, cont, txCont) - updateControlSequence!(cont, newTx) - return true - else - @warn "New control values are not allowed" - return false +function calcDesiredField(cont::AWControlSequence) + # generate desired spectrum per control channel that can be compared to the result of calcFieldsFromRef in the controlStep later + # size: (rfft of sequence x num control channels) + # the separation of individual components is done by the index masks + + desiredField = zeros(ComplexF64, controlMatrixShape(cont)) + + for (i, channel) in enumerate(getControlledChannels(cont.targetSequence)) + + if cont.rfftIndices[i,end,1] + desiredField[1,i] = ustrip(u"T", offset(channel)) + end + + for (j, comp) in components(channel) + if isa(comp, PeriodicElectricalComponent) + desiredField[i, cont.rfftIndices[i,j,:]] = ustrip(u"T",amplitude(comp)) * exp(im*ustrip(u"rad",phase(comp)-pi/2)) # The phase given in the component is for a sine, but the FFT-phase uses a cosine + elseif isa(comp, ArbitraryElectricalComponent) + desiredField[i, cont.rfftIndices[i,j,:]] .= rfft(ustrip.(u"T",values(comp)))[2:sum(cont.rfftIndices[i,j,:])+1]/(0.5*2^14) # the buffer length should always be 2^14 currently + end + end end + + return desiredField end -# Γ: Matrix from Ref -# Ω: Desired Matrix -# κ: Last Set Matrix -function updateControlMatrix(Γ::Matrix, Ω::Matrix, κ::Matrix; correct_coupling::Bool = false) - if correct_coupling - β = Γ*inv(κ) - else - β = diagm(diag(Γ))*inv(diagm(diag(κ))) + +# Convert Last Tx (currSequence) to Matrix in V +function calcControlMatrix(cont::CrossCouplingControlSequence) + κ = zeros(ComplexF64, controlMatrixShape(cont)) + for (i, channel) in enumerate(getControlledChannels(cont)) + for (j, comp) in periodicElectricalComponents(channel) + κ[i, j] = ustrip(u"V", amplitude(comp)) * exp(im*ustrip(u"rad", phase(comp))) + end end - newTx = inv(β)*Ω - @debug "Last matrix:" κ - @debug "Ref matrix" Γ - @debug "Desired matrix" Ω - @debug "New matrix" newTx - return newTx + return κ end -function calcControlMatrix(cont::ControlSequence) - # TxDAQController only works on one field atm (possible future TODO: update to multiple fields, matrix per field) - field = fields(cont.currSequence)[1] - len = numControlledChannels(cont) - κ = zeros(ComplexF64, len, len) - # In each channel the first component is the channels "own" component, the following are the ordered correction components of the other channel - # -> For Channel 2 its components in the matrix row should be c2 c1 c3 for a 3x3 matrix - for (i, channel) in enumerate([channel for channel in periodicElectricalTxChannels(field) if length(arbitraryElectricalComponents(channel)) == 0]) - next = 2 - comps = periodicElectricalComponents(channel) - for j = 1:len - comp = nothing - if (i == j) - comp = comps[1] - elseif next <= length(comps) - comp = comps[next] - next+=1 +function calcControlMatrix(cont::AWControlSequence) + oldTx = zeros(ComplexF64, size(cont.rfftIndices)[3:4]...) + for (i, channel) in enumerate(getControlledChannels(cont)) + if cont.rfftIndices[i,end,1] + oldTx[i,1] = ustrip(u"V", offset(channel)) + end + for (j, comp) in components(channel) + if isa(comp, PeriodicElectricalComponent) + oldTx[i, cont.rfftIndices[i,j,:]] = ustrip(u"V",amplitude(comp)) * exp(im*ustrip(u"rad",phase(comp)-pi/2)) # The phase given in the component is for a sine, but the FFT-phase uses a cosine + elseif isa(comp, ArbitraryElectricalComponent) + oldTx[i, cont.rfftIndices[i,j,:]] .= rfft(ustrip.(u"V",values(comp)))[2:sum(cont.rfftIndices[i,j,:])+1]/(0.5*2^14) # the buffer length should always be 2^14 currently end + end + end + return oldTx +end - val = 0.0 - if !isnothing(comp) - r = ustrip(u"V", amplitude(comp)) - angle = ustrip(u"rad", phase(comp)) - val = r*cos(angle) + r*sin(angle)*im - end - κ[i, j] = val + +# Convert New Tx from matrix in V to currSequence +function updateControlSequence!(cont::CrossCouplingControlSequence, newTx::Matrix) + for (i, channel) in enumerate(periodicElectricalTxChannels(cont.currSeq)) + for (j, comp) in enumerate(periodicElectricalComponents(channel)) + amplitude!(comp, abs(newTx[i, j])*1.0u"V") + phase!(comp, angle(newTx[i, j])*1.0u"rad") end end - return κ end -function updateControlSequence!(cont::ControlSequence, newTx::Matrix) - # TxDAQController only works on one field atm (possible future TODO: update to multiple fields, matrix per field) - field = fields(cont.currSequence)[1] - for (i, channel) in enumerate([channel for channel in periodicElectricalTxChannels(field) if length(arbitraryElectricalComponents(channel)) == 0]) - comps = periodicElectricalComponents(channel) - j = 1 - for (k, comp) in enumerate(comps) - val = 0.0 - # First component is a diagonal entry from the matrix - if k == 1 - val = newTx[i, i] - # All other components are "in order" and skip the diagonal entry - else - if j == i - j+=1 - end - val = newTx[i, j] - j+=1 +function updateControlSequence!(cont::AWControlSequence, newTx::Matrix) + for (i, channel) in enumerate(periodicElectricalTxChannels(cont.currSeq)) + if cont.rfftIndices[i,end,1] + offset!(channel, newTx[i,1]*1.0u"V") + end + for (j, comp) in enumerate(periodicElectricalComponents(channel)) + if isa(comp, PeriodicElectricalComponent) + amplitude!(comp, abs(newTx[i, cont.rfftIndices[i,j,:]])*1.0u"V") + phase!(comp, angle(newTx[i, cont.rfftIndices[i,j,:]])*1.0u"rad"+(pi/2)u"rad") + isa(comp, ArbitraryElectricalComponent) + spectrum = zeros(ComplexF64, 2^13+1) + spectrum[2:sum(cont.rfftIndices[i,j,:])+1] .= newTx[i, cont.rfftIndices[i,j,:]] + values!(comp, irfft(spectrum, 2^14)*(0.5*2^14)*u"V") end - amplitude!(comp, abs(val)*1.0u"V") - phase!(comp, angle(val)*1.0u"rad") end end - end -function checkFieldToVolt(oldTx, Γ, cont::ControlSequence, txCont::TxDAQController) - dividers = Int64[divider(components(channel)[1]) for channel in keys(cont.controlledChannelsDict)] +################################################################################# +########## Functions for checking the matrix representation for safety and plausibility +################################################################################# + + + +function checkFieldToVolt(oldTx::Matrix{<:Complex}, Γ::Matrix{<:Complex}, cont::CrossCouplingControlSequence, txCont::TxDAQController) + dividers = Int64[divider.(getPrimaryComponents(cont))] frequencies = ustrip(u"Hz", txBaseFrequency(cont.currSequence)) ./ dividers - calibFieldToVoltEstimate = [ustrip(u"V/T", collect(Base.values(cont.controlledChannelsDict))[idx].calibration(frequencies[idx])) for idx in 1:numControlledChannels(cont)] + calibFieldToVoltEstimate = [ustrip(u"V/T", chan.calibration(frequencies[i])) for (i,chan) in enumerate(getControlledDAQChannels(cont))] calibFieldToVoltMeasured = (diag(oldTx) ./ diag(Γ)) + abs_deviation = 1.0 .- abs.(calibFieldToVoltMeasured./calibFieldToVoltEstimate) phase_deviation = angle.(calibFieldToVoltMeasured./calibFieldToVoltEstimate) @debug "We expected $(calibFieldToVoltEstimate) and got $(calibFieldToVoltMeasured), deviation: $abs_deviation" @@ -512,16 +755,35 @@ function checkFieldToVolt(oldTx, Γ, cont::ControlSequence, txCont::TxDAQControl if !valid @warn "Measured field to volt deviates by $abs_deviation from estimate, exceeding allowed deviation" elseif maximum(abs.(phase_deviation)) > 10/180*pi - @warn "The phase of the measured field to volt deviates by $phase_deviation from estimate. Continuing anyways..." + @warn "The phase of the measured field to volt deviates by $phase_deviation from estimate. Please check you phases! Continuing anyways..." + end + return valid +end + +function checkFieldToVolt(oldTx::Matrix{<:Complex}, Γ::Matrix{<:Complex}, cont::AWControlSequence, txCont::TxDAQController) + N = rxNumSamplingPoints(cont.currSequence) + frequencies = rfftfreq(N, rxSamplingRate(cont.currSequence)) + calibFieldToVoltEstimate = reduce(vcat,[ustrip.(u"V/T", chan.calibration(frequencies)) for chan in getControlledDAQChannels(cont)]') + calibFieldToVoltMeasured = oldTx ./ Γ + + abs_deviation = 1.0 .- abs.(calibFieldToVoltMeasured[cont.rfftIndices,:]./calibFieldToVoltEstimate[cont.rfftIndices,:]) # TODO/JA: fix indicies!!! + phase_deviation = angle.(calibFieldToVoltMeasured[cont.rfftIndices,:]./calibFieldToVoltEstimate[cont.rfftIndices,:]) + @debug "We expected $(calibFieldToVoltEstimate) and got $(calibFieldToVoltMeasured), deviation: $abs_deviation" + valid = maximum( abs_deviation ) < txCont.params.fieldToVoltDeviation + if !valid + @warn "Measured field to volt deviates by $abs_deviation from estimate, exceeding allowed deviation" + elseif maximum(abs.(phase_deviation)) > 10/180*pi + @warn "The phase of the measured field to volt deviates by $phase_deviation from estimate. Please check you phases! Continuing anyways..." end return valid end -function checkVoltLimits(newTx, cont::ControlSequence, txCont::TxDAQController) - validChannel = zeros(Bool, size(newTx, 2)) - for i = 1:size(newTx, 2) + +function checkVoltLimits(newTx::Matrix{<:Complex}, cont::CrossCouplingControlSequence) + validChannel = zeros(Bool, size(newTx, 1)) + for i = 1:size(newTx, 1) max = sum(abs.(newTx[i, :])) - validChannel[i] = max < ustrip(u"V", collect(Base.values(cont.controlledChannelsDict))[i].limitPeak) + validChannel[i] = max < ustrip(u"V", getControlledDAQChannels(cont)[i].limitPeak) end valid = all(validChannel) if !valid @@ -531,6 +793,17 @@ function checkVoltLimits(newTx, cont::ControlSequence, txCont::TxDAQController) return valid end -function close(txCont::TxDAQController) - # NOP -end \ No newline at end of file +function checkVoltLimits(newTx::Matrix{<:Complex}, cont::AWControlSequence) + validChannel = zeros(Bool, numControlledChannels(cont)) + + testSignalTime = irfft(newTx, lcm(dfDivider(cont.currSequence)), 2) + + validChannel = maximum(abs.(testSignalTime), dims=2) .< ustrip.(u"V", getproperty.(getControlledDAQChannels(cont),:limitPeak)) + + valid = all(validChannel) + if !valid + @debug "Valid Tx Channel" validChannel + @warn "New control sequence exceeds voltage limits of tx channel" + end + return valid +end diff --git a/src/Protocols/RobotBasedSystemMatrixProtocol.jl b/src/Protocols/RobotBasedSystemMatrixProtocol.jl index 50d8475c..45024dcf 100644 --- a/src/Protocols/RobotBasedSystemMatrixProtocol.jl +++ b/src/Protocols/RobotBasedSystemMatrixProtocol.jl @@ -260,10 +260,10 @@ function prepareDAQ(protocol::RobotBasedSystemMatrixProtocol) protocol.restored = false end if isempty(protocol.systemMeasState.drivefield) - len = numControlledChannels(protocol.contSequence) - drivefield = zeros(ComplexF64, len, len, size(calib.signals, 3), size(calib.signals, 4)) + bufferShape = controlMatrixShape(protocol.contSequence) + drivefield = zeros(ComplexF64, bufferShape[1], bufferShape[2], size(calib.signals, 3), size(calib.signals, 4)) calib.drivefield = mmap!(protocol, "observedField.bin", drivefield) - applied = zeros(ComplexF64, len, len, size(calib.signals, 3), size(calib.signals, 4)) + applied = zeros(ComplexF64, bufferShape[1], bufferShape[2], size(calib.signals, 3), size(calib.signals, 4)) calib.applied = mmap!(protocol, "appliedFiled.bin", applied) end sequence = protocol.contSequence diff --git a/src/Protocols/Storage/ChainableBuffer.jl b/src/Protocols/Storage/ChainableBuffer.jl index beffd321..8cd8b96c 100644 --- a/src/Protocols/Storage/ChainableBuffer.jl +++ b/src/Protocols/Storage/ChainableBuffer.jl @@ -148,8 +148,8 @@ end function TxDAQControllerBuffer(tx::TxDAQController, sequence::ControlSequence) numFrames = acqNumFrames(sequence.targetSequence) numPeriods = acqNumPeriodsPerFrame(sequence.targetSequence) - len = numControlledChannels(sequence) - buffer = zeros(ComplexF64, len, len, numPeriods, numFrames) + bufferShape = controlMatrixShape(sequence) + buffer = zeros(ComplexF64, bufferShape[1], bufferShape[2], numPeriods, numFrames) return TxDAQControllerBuffer(1, buffer, tx) end update!(buffer::TxDAQControllerBuffer, start, stop) = insert!(buffer, calcControlMatrix(buffer.tx.cont), start, stop) diff --git a/src/Protocols/Storage/ProducerConsumer.jl b/src/Protocols/Storage/ProducerConsumer.jl index 3f87b1d5..572e200a 100644 --- a/src/Protocols/Storage/ProducerConsumer.jl +++ b/src/Protocols/Storage/ProducerConsumer.jl @@ -2,8 +2,8 @@ SequenceMeasState(x, sequence::ControlSequence, sequenceBuffer::Nothing = nothin function SequenceMeasState(x, sequence::ControlSequence, sequenceBuffer::Vector{StorageBuffer}) numFrames = acqNumFrames(sequence.targetSequence) numPeriods = acqNumPeriodsPerFrame(sequence.targetSequence) - len = numControlledChannels(sequence) - buffer = DriveFieldBuffer(1, zeros(ComplexF64, len, len, numPeriods, numFrames), sequence) + bufferShape = controlMatrixShape(sequence) + buffer = DriveFieldBuffer(1, zeros(ComplexF64, bufferShape[1], bufferShape[2], numPeriods, numFrames), sequence) avgFrames = acqNumFrameAverages(sequence.targetSequence) if avgFrames > 1 samples = rxNumSamplesPerPeriod(sequence.targetSequence) From 5da7f391b40917766ec879555d054e490a3c3293 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Mon, 14 Aug 2023 17:12:52 +0200 Subject: [PATCH 012/168] first set of bug fixes construction of ControlSequence --- src/Devices/DAQ/DAQ.jl | 6 +++--- src/Devices/Virtual/TxDAQController.jl | 21 +++++++++++---------- src/Sequences/PeriodicElectricalChannel.jl | 2 +- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/Devices/DAQ/DAQ.jl b/src/Devices/DAQ/DAQ.jl index 25e3403e..137aaf02 100644 --- a/src/Devices/DAQ/DAQ.jl +++ b/src/Devices/DAQ/DAQ.jl @@ -234,13 +234,13 @@ function applyForwardCalibration!(seq::Sequence, daq::AbstractDAQ) for channel in periodicElectricalTxChannels(seq) offsetVolts = offset(channel)*calibration(daq, id(channel))(0) # use DC value for offsets - offset!(channel, offsetVolts) + offset!(channel, uconvert(u"V",abs(offsetVolts))) for comp in periodicElectricalComponents(channel) amp = amplitude(comp) pha = phase(comp) if dimension(amp) != dimension(1.0u"V") - f_comp = ustrip(u"Hz", txBaseFrequency(sequence)) / divider(comp) + f_comp = ustrip(u"Hz", txBaseFrequency(seq)) / divider(comp) complex_comp = (amp*exp(im*pha)) * calibration(daq, id(channel))(f_comp) amplitude!(comp,uconvert(u"V",abs(complex_comp))) phase!(comp,angle(complex_comp)u"rad") @@ -250,7 +250,7 @@ function applyForwardCalibration!(seq::Sequence, daq::AbstractDAQ) for aw_comp in arbitraryElectricalComponents(channel) wave = values(aw_comp) if dimension(wave[1]) != dimension(1.0u"V") - f_comp = ustrip(u"Hz", txBaseFrequency(sequence)) / divider(comp) + f_comp = ustrip(u"Hz", txBaseFrequency(seq)) / divider(comp) f_awg = rfftfreq(2^14, f_comp*2^14) wave = irfft(rfft(wave).*calibration(daq, name)(f_awg), 2^14) values!(aw_comp, wave) diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index 374922c5..00b92abf 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -58,7 +58,7 @@ optionalDependencies(::TxDAQController) = [SurveillanceUnit, Amplifier, Temperat function ControlSequence(txCont::TxDAQController, target::Sequence, daq::AbstractDAQ) - if any(!isa.(getControlledChannels(target), PeriodicElectricalChannel)) + if any(.!isa.(getControlledChannels(target), PeriodicElectricalChannel)) error("A field that requires control can only have PeriodicElectricalChannels.") # TODO/JA: check if this limitation can be lifted: would require special care when merging a sequence end @@ -71,7 +71,7 @@ function ControlSequence(txCont::TxDAQController, target::Sequence, daq::Abstrac controlledChannelsDict = createControlledChannelsDict(seqControlledChannels, daq) # should this be changed to components instead of channels? refIndices = createReferenceIndexMapping(controlledChannelsDict, daq) - if txCont.correctCrossCoupling # use the old controller # TODO/JA: fix logic to decide which controller should be used + if txCont.params.correctCrossCoupling # use the old controller # TODO/JA: fix logic to decide which controller should be used if length(fields(currSeq)) > 1 throw(IllegalStateException("Sequence requires control of multiple fields, which is currently not implemented with cross-coupling correction.")) @@ -113,14 +113,14 @@ function ControlSequence(txCont::TxDAQController, target::Sequence, daq::Abstrac else # use the new controller # AW offset gets ignored -> should be zero anyways - if any(x->mean.(values.(arbitraryElectricalComponents(x))).>1u"µT", seqControlledChannels) + if any(x->any(mean.(values.(arbitraryElectricalComponents(x))).>1u"µT"), seqControlledChannels) error("The DC-component of arbitrary waveform components cannot be handled during control! Please remove any DC-offset from your waveform and use the offset parameter of the corresponding channel!") end # all channels on the same ref must agree with their offset -> just choose one of the channels that has the dcEnabled Flag - rfftIndices = createRFFTindices(controlledChannelsDict, target) + rfftIndices = createRFFTindices(controlledChannelsDict, target, daq) return AWControlSequence(target, currSeq, controlledChannelsDict, refIndices, rfftIndices) end @@ -135,15 +135,15 @@ function createRFFTindices(controlledChannelsDict::OrderedDict{PeriodicElectrica # N for every single-frequency component that has N periods in the sequence # every Mth sample for all arbitraryWaveform components numControlledChannels = length(keys(controlledChannelsDict)) - rfftSize = div(lcm(dfDivider(seq)),2)+1 + rfftSize = Int(div(lcm(dfDivider(seq))/(upreferred(txBaseFrequency(seq)/rxSamplingRate(seq))),2)+1) index_mask = falses(numControlledChannels, numComponentsMax(daq)+1, rfftSize) refChannelIdx = [channelIdx(daq, ch.feedback.channelID) for ch in collect(Base.values(controlledChannelsDict))] for (i, channel) in enumerate(keys(controlledChannelsDict)) - for (j, comp) in components(channel) - dfCyclesPerPeriod = lcm(dfDivider(seq))/divider(comp) + for (j, comp) in enumerate(components(channel)) + dfCyclesPerPeriod = Int(lcm(dfDivider(seq))/divider(comp)) if isa(comp, PeriodicElectricalComponent) index_mask[i,j,dfCyclesPerPeriod+1] = true elseif isa(comp, ArbitraryElectricalComponent) @@ -161,8 +161,8 @@ function createRFFTindices(controlledChannelsDict::OrderedDict{PeriodicElectrica uniqueRefChIdx = unique(refChannelIdx) combinedRefChannelMask = falses(length(uniqueRefChIdx), size(allComponentMask, 2)) # if two controlled channels are on the same ref channel they should be combined, maybe there is a more elegant way to do this, but I cant figure it out - for i in 1:numControlledChannels - combinedRefChannelMask .|= allComponentMask[findfirst(x->x==refChannelIdx[i], uniqueRefChIdx),:] + for i in 1:numControlledChannels + combinedRefChannelMask[findfirst(x->x==refChannelIdx[i], uniqueRefChIdx),:] .|= allComponentMask[i,:] end # if the total number of trues is smaller than the full array, there is an overlap of different components in the same FFT bin somewhere if sum(combinedRefChannelMask) < sum(index_mask) @@ -261,12 +261,13 @@ function createControlledChannelsDict(seqControlledChannels::Vector{PeriodicElec name = id(seqChannel) daqChannel = get(daq.params.channels, name, nothing) if isnothing(daqChannel) || isnothing(daqChannel.feedback) || !in(daqChannel.feedback.channelID, daq.refChanIDs) + @debug "Found missing control def: " name isnothing(daqChannel) isnothing(daqChannel.feedback) !in(daqChannel.feedback.channelID, daq.refChanIDs) push!(missingControlDef, name) else dict[seqChannel] = daqChannel end end - + if length(missingControlDef) > 0 message = "The sequence requires control for the following channel " * join(string.(missingControlDef), ", ", " and ") * ", but either the channel was not defined or had no defined feedback channel." throw(IllegalStateException(message)) diff --git a/src/Sequences/PeriodicElectricalChannel.jl b/src/Sequences/PeriodicElectricalChannel.jl index c55697b9..1fd624c4 100644 --- a/src/Sequences/PeriodicElectricalChannel.jl +++ b/src/Sequences/PeriodicElectricalChannel.jl @@ -37,7 +37,7 @@ end """Electrical channel based on based on periodic base functions. Only the PeriodicElectricalChannel counts for the cycle length calculation""" -Base.@kwdef struct PeriodicElectricalChannel <: ElectricalTxChannel +Base.@kwdef mutable struct PeriodicElectricalChannel <: ElectricalTxChannel "ID corresponding to the channel configured in the scanner." id::AbstractString "Components added for this channel." From 4fad273249c407d2bbde8aacc7a88d90de645f98 Mon Sep 17 00:00:00 2001 From: Justin Ackers Date: Tue, 15 Aug 2023 14:10:55 +0200 Subject: [PATCH 013/168] added option to not change sequence in place --- src/Devices/DAQ/DAQ.jl | 10 +++++++++- src/Devices/DAQ/RedPitayaDAQ.jl | 10 +++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/Devices/DAQ/DAQ.jl b/src/Devices/DAQ/DAQ.jl index 137aaf02..6a75da5b 100644 --- a/src/Devices/DAQ/DAQ.jl +++ b/src/Devices/DAQ/DAQ.jl @@ -226,7 +226,14 @@ function calibration(daq::AbstractDAQ, channelID::AbstractString) end -export applyForwardCalibration! +export applyForwardCalibration!, applyForwardCalibration + +function applyForwardCalibration(seq::Sequence, daq::AbstractDAQ) + seqCopy = deepcopy(seq) + applyForwardCalibration!(seqCopy, daq) + return seqCopy +end + function applyForwardCalibration!(seq::Sequence, daq::AbstractDAQ) # TODO/JA: what about the other types of channels? @@ -258,6 +265,7 @@ function applyForwardCalibration!(seq::Sequence, daq::AbstractDAQ) end end + nothing end diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index a47433e8..dd997bd7 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -508,17 +508,17 @@ end function setupTx(daq::RedPitayaDAQ, sequence::Sequence) @debug "Setup tx" - periodicChannels = periodicElectricalTxChannels(sequence) + sequenceVolt = applyForwardCalibration(sequence, daq) + + periodicChannels = periodicElectricalTxChannels(sequenceVolt) if any([length(component.amplitude) > 1 for channel in periodicChannels for component in periodicElectricalComponents(channel)]) error("The Red Pitaya DAQ cannot work with more than one period in a frame or frequency sweeps yet.") end - - applyForwardCalibration!(sequence, daq) - + # Iterate over sequence(!) channels execute!(daq.rpc) do batch - baseFreq = txBaseFrequency(sequence) + baseFreq = txBaseFrequency(sequenceVolt) for channel in periodicChannels setupTxChannel!(batch, daq, channel, baseFreq) end From 6252ac3d04133bfd3aee2e1c6d31b07c8374c445 Mon Sep 17 00:00:00 2001 From: Justin Ackers Date: Tue, 15 Aug 2023 17:05:10 +0200 Subject: [PATCH 014/168] fix dimension --- src/Devices/Virtual/TxDAQController.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index 00b92abf..3b1095ba 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -692,7 +692,7 @@ function calcControlMatrix(cont::CrossCouplingControlSequence) end function calcControlMatrix(cont::AWControlSequence) - oldTx = zeros(ComplexF64, size(cont.rfftIndices)[3:4]...) + oldTx = zeros(ComplexF64, controlMatrixShape(cont)) for (i, channel) in enumerate(getControlledChannels(cont)) if cont.rfftIndices[i,end,1] oldTx[i,1] = ustrip(u"V", offset(channel)) From c3f9d412f091b391ae2da8f373fc756d8db5f807 Mon Sep 17 00:00:00 2001 From: Justin Ackers Date: Thu, 17 Aug 2023 11:07:14 +0200 Subject: [PATCH 015/168] remove attempt to decouple, add warning --- src/Devices/Virtual/TxDAQController.jl | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index 3b1095ba..e5c25beb 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -541,14 +541,9 @@ function updateControlMatrix(::CrossCouplingControlSequence, txCont::TxDAQContro end function updateControlMatrix(cont::AWControlSequence, txCont::TxDAQController, Γ::Matrix{<:Complex}, Ω::Matrix{<:Complex}, κ::Matrix{<:Complex}) - # TODO/JA: can we divide by zero here accidentally? Will the fieldtoVolt check fail, if we include the components, that are not part of the signal - newTx = zeros(ComplexF64, controlMatrixShape(cont)) - # potentially blank out every component that we are not controlling? -> maybe not necessary, but could increase noise - β = Γ[:,2:end] / κ[:,2:end] - newTx[:,2:end] = inv(β) * Ω[:,2:end] - - # no coupling for DC! - newTx[:,1] = κ[:,1]./Γ[:,1].*Ω[:,1] + # For now we completely ignore coupling and hope that it can find good values anyways + # The problem is, that to achieve 0 we will always output zero, but we would need a much more sophisticated method to solve this + newTx = κ./Γ.*Ω @debug "Last matrix:" κ @debug "Ref matrix" Γ @@ -670,6 +665,9 @@ function calcDesiredField(cont::AWControlSequence) for (j, comp) in components(channel) if isa(comp, PeriodicElectricalComponent) + if ustrip(u"T",amplitude(comp)) == 0 + @warn "You tried to control a field to 0 T, this will just output 0 V on that channel, since this controller can not correct cross coupling" + end desiredField[i, cont.rfftIndices[i,j,:]] = ustrip(u"T",amplitude(comp)) * exp(im*ustrip(u"rad",phase(comp)-pi/2)) # The phase given in the component is for a sine, but the FFT-phase uses a cosine elseif isa(comp, ArbitraryElectricalComponent) desiredField[i, cont.rfftIndices[i,j,:]] .= rfft(ustrip.(u"T",values(comp)))[2:sum(cont.rfftIndices[i,j,:])+1]/(0.5*2^14) # the buffer length should always be 2^14 currently From 6d785bbcb5acf5979d2757fef3e08c9ef52c1b22 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Thu, 17 Aug 2023 17:06:23 +0200 Subject: [PATCH 016/168] small fixes --- src/Devices/Virtual/TxDAQController.jl | 39 +++++++++++++++----------- src/Sequences/Sequence.jl | 2 +- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index e5c25beb..81348db2 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -135,7 +135,7 @@ function createRFFTindices(controlledChannelsDict::OrderedDict{PeriodicElectrica # N for every single-frequency component that has N periods in the sequence # every Mth sample for all arbitraryWaveform components numControlledChannels = length(keys(controlledChannelsDict)) - rfftSize = Int(div(lcm(dfDivider(seq))/(upreferred(txBaseFrequency(seq)/rxSamplingRate(seq))),2)+1) + rfftSize = Int(div(rxNumSamplingPoints(seq),2)+1) index_mask = falses(numControlledChannels, numComponentsMax(daq)+1, rfftSize) refChannelIdx = [channelIdx(daq, ch.feedback.channelID) for ch in collect(Base.values(controlledChannelsDict))] @@ -432,7 +432,7 @@ function getControlResult(cont::ControlSequence)::Sequence acq = cont.target.acquisition _fields = MagneticField[] - for field in fields(cont.currSeq) + for field in fields(cont.currSequence) _id = id(field) safeStart = safeStartInterval(field) safeTrans = safeTransitionInterval(field) @@ -458,9 +458,9 @@ setup(daq::AbstractDAQ, sequence::ControlSequence) = setup(daq, getControlResult # TODO/JA: check if changes here needs changes somewhere else getControlledFields(seq::Sequence) = [field for field in seq.fields if field.control] getControlledChannels(seq::Sequence) = [channel for field in seq.fields if field.control for channel in field.channels] -# TODO/JA: is getControlledChannels(cont) always equal to getControlledChannels(cont.currSeq) ?? +# The elements of collect(getControlledChannels(cont)) are always identical (===) to getControlledChannels(cont.currSequence) getControlledChannels(cont::ControlSequence) = keys(cont.controlledChannelsDict) # maybe add collect here as well? for most uses this not needed -getControlledDAQChannels(cont::ControlSequence) = collect(Base.values(cont.contrlledChannelsDict)) +getControlledDAQChannels(cont::ControlSequence) = collect(Base.values(cont.controlledChannelsDict)) getPrimaryComponents(cont::CrossCouplingControlSequence) = [components(channel)[i] for (i,channel) in enumerate(getControlledChannels(cont))] acyclicElectricalTxChannels(cont::ControlSequence) = acyclicElectricalTxChannels(cont.targetSequence) @@ -663,12 +663,12 @@ function calcDesiredField(cont::AWControlSequence) desiredField[1,i] = ustrip(u"T", offset(channel)) end - for (j, comp) in components(channel) + for (j, comp) in enumerate(components(channel)) if isa(comp, PeriodicElectricalComponent) if ustrip(u"T",amplitude(comp)) == 0 @warn "You tried to control a field to 0 T, this will just output 0 V on that channel, since this controller can not correct cross coupling" end - desiredField[i, cont.rfftIndices[i,j,:]] = ustrip(u"T",amplitude(comp)) * exp(im*ustrip(u"rad",phase(comp)-pi/2)) # The phase given in the component is for a sine, but the FFT-phase uses a cosine + desiredField[i, cont.rfftIndices[i,j,:]] .= ustrip(u"T",amplitude(comp)) * exp(im*ustrip(u"rad",phase(comp)-pi/2)) # The phase given in the component is for a sine, but the FFT-phase uses a cosine elseif isa(comp, ArbitraryElectricalComponent) desiredField[i, cont.rfftIndices[i,j,:]] .= rfft(ustrip.(u"T",values(comp)))[2:sum(cont.rfftIndices[i,j,:])+1]/(0.5*2^14) # the buffer length should always be 2^14 currently end @@ -682,7 +682,7 @@ end function calcControlMatrix(cont::CrossCouplingControlSequence) κ = zeros(ComplexF64, controlMatrixShape(cont)) for (i, channel) in enumerate(getControlledChannels(cont)) - for (j, comp) in periodicElectricalComponents(channel) + for (j, comp) in enumerate(periodicElectricalComponents(channel)) κ[i, j] = ustrip(u"V", amplitude(comp)) * exp(im*ustrip(u"rad", phase(comp))) end end @@ -695,9 +695,9 @@ function calcControlMatrix(cont::AWControlSequence) if cont.rfftIndices[i,end,1] oldTx[i,1] = ustrip(u"V", offset(channel)) end - for (j, comp) in components(channel) + for (j, comp) in enumerate(components(channel)) if isa(comp, PeriodicElectricalComponent) - oldTx[i, cont.rfftIndices[i,j,:]] = ustrip(u"V",amplitude(comp)) * exp(im*ustrip(u"rad",phase(comp)-pi/2)) # The phase given in the component is for a sine, but the FFT-phase uses a cosine + oldTx[i, cont.rfftIndices[i,j,:]] .= ustrip(u"V",amplitude(comp)) * exp(im*ustrip(u"rad",phase(comp)-pi/2)) # The phase given in the component is for a sine, but the FFT-phase uses a cosine elseif isa(comp, ArbitraryElectricalComponent) oldTx[i, cont.rfftIndices[i,j,:]] .= rfft(ustrip.(u"V",values(comp)))[2:sum(cont.rfftIndices[i,j,:])+1]/(0.5*2^14) # the buffer length should always be 2^14 currently end @@ -709,7 +709,7 @@ end # Convert New Tx from matrix in V to currSequence function updateControlSequence!(cont::CrossCouplingControlSequence, newTx::Matrix) - for (i, channel) in enumerate(periodicElectricalTxChannels(cont.currSeq)) + for (i, channel) in enumerate(periodicElectricalTxChannels(cont.currSequence)) for (j, comp) in enumerate(periodicElectricalComponents(channel)) amplitude!(comp, abs(newTx[i, j])*1.0u"V") phase!(comp, angle(newTx[i, j])*1.0u"rad") @@ -718,15 +718,15 @@ function updateControlSequence!(cont::CrossCouplingControlSequence, newTx::Matri end function updateControlSequence!(cont::AWControlSequence, newTx::Matrix) - for (i, channel) in enumerate(periodicElectricalTxChannels(cont.currSeq)) + for (i, channel) in enumerate(periodicElectricalTxChannels(cont.currSequence)) if cont.rfftIndices[i,end,1] - offset!(channel, newTx[i,1]*1.0u"V") + offset!(channel, abs(newTx[i,1])*1.0u"V") end for (j, comp) in enumerate(periodicElectricalComponents(channel)) if isa(comp, PeriodicElectricalComponent) - amplitude!(comp, abs(newTx[i, cont.rfftIndices[i,j,:]])*1.0u"V") - phase!(comp, angle(newTx[i, cont.rfftIndices[i,j,:]])*1.0u"rad"+(pi/2)u"rad") - isa(comp, ArbitraryElectricalComponent) + amplitude!(comp, abs.(newTx[i, cont.rfftIndices[i,j,:]])[]*1.0u"V") + phase!(comp, angle.(newTx[i, cont.rfftIndices[i,j,:]])[]*1.0u"rad"+(pi/2)u"rad") + elseif isa(comp, ArbitraryElectricalComponent) spectrum = zeros(ComplexF64, 2^13+1) spectrum[2:sum(cont.rfftIndices[i,j,:])+1] .= newTx[i, cont.rfftIndices[i,j,:]] values!(comp, irfft(spectrum, 2^14)*(0.5*2^14)*u"V") @@ -792,10 +792,11 @@ function checkVoltLimits(newTx::Matrix{<:Complex}, cont::CrossCouplingControlSeq return valid end -function checkVoltLimits(newTx::Matrix{<:Complex}, cont::AWControlSequence) +function checkVoltLimits(newTx::Matrix{<:Complex}, cont::AWControlSequence; return_time_signal=false) validChannel = zeros(Bool, numControlledChannels(cont)) + N = rxNumSamplingPoints(cont.currSequence) - testSignalTime = irfft(newTx, lcm(dfDivider(cont.currSequence)), 2) + testSignalTime = irfft(newTx, N, 2)*0.5N validChannel = maximum(abs.(testSignalTime), dims=2) .< ustrip.(u"V", getproperty.(getControlledDAQChannels(cont),:limitPeak)) @@ -804,5 +805,9 @@ function checkVoltLimits(newTx::Matrix{<:Complex}, cont::AWControlSequence) @debug "Valid Tx Channel" validChannel @warn "New control sequence exceeds voltage limits of tx channel" end + if return_time_signal + return testSignalTime + else return valid + end end diff --git a/src/Sequences/Sequence.jl b/src/Sequences/Sequence.jl index 8b8a90d1..e508d64e 100644 --- a/src/Sequences/Sequence.jl +++ b/src/Sequences/Sequence.jl @@ -327,7 +327,7 @@ export dfDivider function dfDivider(sequence::Sequence) # TODO: How do we integrate the mechanical channels and non-periodic channels and sweeps? channels = dfChannels(sequence) maxComponents = maximum([length(channel.components) for channel in channels]) - result = zeros(Int64, (dfNumChannels(sequence), maxComponents)) + result = ones(Int64, (dfNumChannels(sequence), maxComponents)) # Otherwise the dfCycle is miscalculated in the case of a different amount of components for (channelIdx, channel) in enumerate(channels) for (componentIdx, component) in enumerate(channel.components) From dbd6f04dedd24d47b999ca5cdcbd1dbc27b8e80e Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Thu, 17 Aug 2023 17:26:11 +0200 Subject: [PATCH 017/168] improve logic on when and how to control --- src/Devices/Virtual/TxDAQController.jl | 92 +++++++++++++------------- src/Sequences/MagneticField.jl | 2 +- src/Sequences/Sequence.jl | 5 ++ 3 files changed, 53 insertions(+), 46 deletions(-) diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index 81348db2..a3ecb5a2 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -6,7 +6,6 @@ Base.@kwdef mutable struct TxDAQControllerParams <: DeviceParams controlPause::Float64 maxControlSteps::Int64 = 20 fieldToVoltDeviation::Float64 = 0.2 - correctCrossCoupling::Bool = false controlDC::Bool = false end TxDAQControllerParams(dict::Dict) = params_from_dict(TxDAQControllerParams, dict) @@ -71,61 +70,59 @@ function ControlSequence(txCont::TxDAQController, target::Sequence, daq::Abstrac controlledChannelsDict = createControlledChannelsDict(seqControlledChannels, daq) # should this be changed to components instead of channels? refIndices = createReferenceIndexMapping(controlledChannelsDict, daq) - if txCont.params.correctCrossCoupling # use the old controller # TODO/JA: fix logic to decide which controller should be used - - if length(fields(currSeq)) > 1 - throw(IllegalStateException("Sequence requires control of multiple fields, which is currently not implemented with cross-coupling correction.")) - end - - # Check that we only control three channels, as our RedPitayaDAQs only have 3 signal components atm - if length(controlledChannelsDict) > 3 - throw(IllegalStateException("Sequence requires controlling of more than three channels, which is not possible with cross-coupling correction.")) - end - - # Check that channels only have one component - if any(x -> length(x.components) > 1, seqControlledChannels) - throw(IllegalStateException("Sequence has channel with more than one component. Such a channel cannot be controlled with cross-coupling correction")) - end - - if any(x -> isa(x.components, ArbitraryElectricalComponent), seqControlledChannels) - throw(IllegalStateException("Sequence has a channel with an ArbitraryElectricalComponent. Such a channel cannot be controlled with cross-coupling correction")) - end + controlSequenceType = decideControlSequenceType(target) + if controlSequenceType==CrossCouplingControlSequence + # temporarily remove first (and only) component from each channel - comps = [popfirst!(periodicElectricalComponents(channel)) for channel in periodicElectricalTxChannels(fields(currSeq)[1])] - - # insert all components into each channel in the same order, all comps from the other channels are set to 0T - for (i, channel) in enumerate(periodicElectricalTxChannels(fields(currSeq)[1])) - for (j, comp) in enumerate(comps) - copy_ = deepcopy(comp) - if i!=j - amplitude!(copy_, 0.0u"T") + comps = [popfirst!(periodicElectricalComponents(channel)) for channel in periodicElectricalTxChannels(fields(currSeq)[1])] + + # insert all components into each channel in the same order, all comps from the other channels are set to 0T + for (i, channel) in enumerate(periodicElectricalTxChannels(fields(currSeq)[1])) + for (j, comp) in enumerate(comps) + copy_ = deepcopy(comp) + if i!=j + amplitude!(copy_, 0.0u"T") + end + push!(channel.components, copy_) end - push!(channel.components, copy_) end - end sinLUT, cosLUT = createLUTs(seqControlledChannels, currSeq) # TODO/JA: check if this makes a difference between target and currSeq, though it should not - return CrossCouplingControlSequence(target, currSeq, controlledChannelsDict, sinLUT, cosLUT, refIndices) - else # use the new controller + elseif controlSequenceType == AWControlSequence # use the new controller - # AW offset gets ignored -> should be zero anyways + # AW offset will get ignored -> should be zero if any(x->any(mean.(values.(arbitraryElectricalComponents(x))).>1u"µT"), seqControlledChannels) error("The DC-component of arbitrary waveform components cannot be handled during control! Please remove any DC-offset from your waveform and use the offset parameter of the corresponding channel!") end - # all channels on the same ref must agree with their offset -> just choose one of the channels that has the dcEnabled Flag - - rfftIndices = createRFFTindices(controlledChannelsDict, target, daq) return AWControlSequence(target, currSeq, controlledChannelsDict, refIndices, rfftIndices) end end +function decideControlSequenceType(target::Sequence) + + hasAWComponent = any(isa.([component for channel in getControlledChannels(target) for component in channel.components], ArbitraryElectricalComponent)) + moreThanOneComponent = any(x -> length(x.components) > 1, getControlledChannels(target)) + moreThanThreeChannels = length(getControlledChannels(target)) > 3 + moreThanOneField = length(getControlledFields(target)) > 1 + needsDecoupling_ = needsDecoupling(target) + + if needsDecoupling_ && !hasAWComponent && !moreThanOneField && !moreThanThreeChannels && !moreThanOneComponent + return CrossCouplingControlSequence + elseif needsDecoupling_ + throw(SequenceConfigurationError("The given sequence can not be controlled! To control a field with decoupling it cannot have an AW component ($hasAWComponent), more than one field ($moreThanOneField), more than three channels ($moreThanThreeChannels) nor more than one component per channel ($moreThanOneComponent)")) + elseif !hasAWComponent && !moreThanOneField && !moreThanThreeChannels && !moreThanOneComponent + return CrossCouplingControlSequence + else + return AWControlSequence + end +end """ This function creates the correct indices into a channel-wise rfft of the reference channels for each channel in the controlledChannelsDict @@ -245,7 +242,7 @@ function prepareSequenceForControl(seq::Sequence) end end contField = MagneticField(;id = _id, channels = periodicChannel, safeStartInterval = safeStart, safeTransitionInterval = safeTrans, - safeEndInterval = safeEnd, safeErrorInterval = safeError, control = true) + safeEndInterval = safeEnd, safeErrorInterval = safeError, decouple = false, control = false) push!(_fields, contField) end end @@ -281,10 +278,15 @@ end ############################################################################### function controlTx(txCont::TxDAQController, seq::Sequence, ::Nothing = nothing) - daq = dependency(txCont, AbstractDAQ) - setupRx(daq, seq) - control = ControlSequence(txCont, seq, daq) # depending on the controlled channels and settings this will select the appropiate type of ControlSequence - return controlTx(txCont, seq, control) + if needsControlOrDecoupling(seq) + daq = dependency(txCont, AbstractDAQ) + setupRx(daq, seq) + control = ControlSequence(txCont, seq, daq) # depending on the controlled channels and settings this will select the appropiate type of ControlSequence + return controlTx(txCont, seq, control) + else + @warn "The sequence you selected does not need control, even though the protocol wanted to control!" + return seq + end end @@ -440,7 +442,7 @@ function getControlResult(cont::ControlSequence)::Sequence safeError = safeErrorInterval(field) #TODO/JA: should the channels be a copy? Is it even necessary to create a new object just to set control to false? Maybe this will work anyways contField = MagneticField(;id = _id, channels = deepcopy(channels(field)), safeStartInterval = safeStart, safeTransitionInterval = safeTrans, - safeEndInterval = safeEnd, safeErrorInterval = safeError, decouple=false, control = false) + safeEndInterval = safeEnd, safeErrorInterval = safeError, decouple = false, control = false) push!(_fields, contField) end for field in fields(cont.target) @@ -526,8 +528,8 @@ end # Γ: Matrix from Ref # Ω: Desired Matrix # κ: Last Set Matrix -function updateControlMatrix(::CrossCouplingControlSequence, txCont::TxDAQController, Γ::Matrix{<:Complex}, Ω::Matrix{<:Complex}, κ::Matrix{<:Complex}) - if txCont.params.correctCrossCoupling +function updateControlMatrix(cont::CrossCouplingControlSequence, txCont::TxDAQController, Γ::Matrix{<:Complex}, Ω::Matrix{<:Complex}, κ::Matrix{<:Complex}) + if needsDecoupling(cont.target) β = Γ*inv(κ) else β = diagm(diag(Γ))*inv(diagm(diag(κ))) @@ -808,6 +810,6 @@ function checkVoltLimits(newTx::Matrix{<:Complex}, cont::AWControlSequence; retu if return_time_signal return testSignalTime else - return valid + return valid end end diff --git a/src/Sequences/MagneticField.jl b/src/Sequences/MagneticField.jl index eb365a3e..4de08ae6 100644 --- a/src/Sequences/MagneticField.jl +++ b/src/Sequences/MagneticField.jl @@ -50,7 +50,7 @@ safeEndInterval(field::MagneticField) = field.safeEndInterval export safeErrorInterval safeErrorInterval(field::MagneticField) = field.safeErrorInterval -control(field::MagneticField) = field.control +control(field::MagneticField) = field.control || field.decouple decouple(field::MagneticField) = field.decouple export electricalTxChannels diff --git a/src/Sequences/Sequence.jl b/src/Sequences/Sequence.jl index e508d64e..70458b83 100644 --- a/src/Sequences/Sequence.jl +++ b/src/Sequences/Sequence.jl @@ -126,6 +126,11 @@ function fieldDictToFields(fieldsDict::Dict{String, Any}) if haskey(fieldDict, "safeErrorInterval") splattingDict[:safeErrorInterval] = uparse(fieldDict["safeErrorInterval"]) end + + if splattingDict["decouple"] && !splattingDict["control"] + @warn "A field that needs to be decoupled, will always be controlled!" + splattingDict["control"] = true + end field = MagneticField(;splattingDict...) push!(fields, field) From 7c0e0fdde6ac4e5e1a18b8f8570ec461fe677cc7 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Thu, 17 Aug 2023 17:46:40 +0200 Subject: [PATCH 018/168] fix parameter check --- src/Sequences/Sequence.jl | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Sequences/Sequence.jl b/src/Sequences/Sequence.jl index 70458b83..456ef826 100644 --- a/src/Sequences/Sequence.jl +++ b/src/Sequences/Sequence.jl @@ -126,10 +126,11 @@ function fieldDictToFields(fieldsDict::Dict{String, Any}) if haskey(fieldDict, "safeErrorInterval") splattingDict[:safeErrorInterval] = uparse(fieldDict["safeErrorInterval"]) end - - if splattingDict["decouple"] && !splattingDict["control"] - @warn "A field that needs to be decoupled, will always be controlled!" - splattingDict["control"] = true + if haskey(splattingDict, :decouple) && splattingDict[:decouple] + if haskey(splattingDict, :control) && !splattingDict[:control] + throw(SequenceConfigurationError("A field that should be decoupled always needs to be controlled, please fix field \"$(fieldID)\"")) + end + splattingDict[:control] = true end field = MagneticField(;splattingDict...) From ed4c87bc89caa12dd17af86515259d25a369ffc5 Mon Sep 17 00:00:00 2001 From: nHackel Date: Fri, 25 Aug 2023 14:09:34 +0200 Subject: [PATCH 019/168] Add function to retrieve Vector of sequences --- Project.toml | 1 + src/MPIMeasurements.jl | 1 + src/Scanner.jl | 10 +++++++++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index f6a8172b..0b7dab39 100644 --- a/Project.toml +++ b/Project.toml @@ -13,6 +13,7 @@ LibSerialPort = "a05a14c7-6e3b-5ba9-90a2-45558833e1df" MPIFiles = "371237a9-e6c1-5201-9adb-3d8cfa78fa9f" MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" Mmap = "a63ad114-7e13-5084-954f-fe012c677804" +NaturalSort = "c020b1a1-e9b0-503a-9c33-f039bfc54a85" ProgressMeter = "92933f4c-e287-5a05-a399-4b506db050ca" REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" RedPitayaDAQServer = "c544963a-496b-56d4-a5fe-f99a3f174c8f" diff --git a/src/MPIMeasurements.jl b/src/MPIMeasurements.jl index 4a1e3505..c7e1fe55 100644 --- a/src/MPIMeasurements.jl +++ b/src/MPIMeasurements.jl @@ -18,6 +18,7 @@ using StringEncodings using DocStringExtensions using MacroTools using LibSerialPort +using NaturalSort using ReplMaker import REPL diff --git a/src/Scanner.jl b/src/Scanner.jl index d0404d21..0c046f4f 100644 --- a/src/Scanner.jl +++ b/src/Scanner.jl @@ -397,13 +397,21 @@ function Sequence(configdir::AbstractString, name::AbstractString) end return sequenceFromTOML(path) end - +function Sequences(configdir::AbstractString, name::AbstractString) + path = joinpath(configdir, "Sequences", name) + if !isdir(path) + error("Sequence-Directory $(path) not available!") + end + paths = sort(collect(readdir(path, join = true)), lt=natural) + return [sequenceFromTOML(p) for p in paths] +end """ $(SIGNATURES) Constructor for a sequence of `name` from the configuration directory specified for the scanner. """ Sequence(scanner::MPIScanner, name::AbstractString) = Sequence(configDir(scanner), name) +Sequences(scanner::MPIScanner, name::AbstractString) = Sequences(configDir(scanner), name) function Sequence(scanner::MPIScanner, dict::Dict) sequence = sequenceFromDict(dict) From c1b37e3ba256bd4a610afdda580873e097ae4c5a Mon Sep 17 00:00:00 2001 From: nHackel Date: Fri, 25 Aug 2023 17:07:34 +0200 Subject: [PATCH 020/168] Init working multi sequence sm protocol --- .../MultiSequenceSystemMatrixProtocol.jl | 473 ++++++++++++++++++ src/Protocols/Protocol.jl | 1 + 2 files changed, 474 insertions(+) create mode 100644 src/Protocols/MultiSequenceSystemMatrixProtocol.jl diff --git a/src/Protocols/MultiSequenceSystemMatrixProtocol.jl b/src/Protocols/MultiSequenceSystemMatrixProtocol.jl new file mode 100644 index 00000000..f903fcfa --- /dev/null +++ b/src/Protocols/MultiSequenceSystemMatrixProtocol.jl @@ -0,0 +1,473 @@ +export MultiSequenceSystemMatrixProtocol, MultiSequenceSystemMatrixProtocolParams +""" +Parameters for the MultiSequenceSystemMatrixProtocol +""" +Base.@kwdef mutable struct MultiSequenceSystemMatrixProtocolParams <: ProtocolParams + "If set the tx amplitude and phase will be set with control steps" + controlTx::Bool = false + "If the temperature should be safed or not" + saveTemperatureData::Bool = false + "Sequences to measure" + sequences::Union{Vector{Sequence},Nothing} = nothing + "SM Positions mapped to the natural sorting of sequence tomls" + positions::Union{Positions, Nothing} = nothing + "Flag if the calibration should be saved as a system matrix or not" + saveAsSystemMatrix::Bool = true +end +function MultiSequenceSystemMatrixProtocolParams(dict::Dict, scanner::MPIScanner) + positions = nothing + if haskey(dict, "Positions") + posDict = dict["Positions"] + + positions = Positions(posDict) + delete!(dict, "Positions") + end + + + sequence = nothing + if haskey(dict, "sequences") + sequences = Sequences(scanner, dict["sequences"]) + pop!(dict, "sequences") + end + params = params_from_dict(MultiSequenceSystemMatrixProtocolParams, dict) + params.sequences = sequences + params.positions = positions + return params +end +MultiSequenceSystemMatrixProtocolParams(dict::Dict) = params_from_dict(MultiSequenceSystemMatrixProtocolParams, dict) + +Base.@kwdef mutable struct MultiSequenceSystemMatrixProtocol <: Protocol + @add_protocol_fields MultiSequenceSystemMatrixProtocolParams + + systemMeasState::Union{SystemMatrixMeasState,Nothing} = nothing + + done::Bool = false + cancelled::Bool = false + finishAcknowledged::Bool = false + stopped::Bool = false + restored::Bool = false + measuring::Bool = false + txCont::Union{TxDAQController,Nothing} = nothing +end + +function requiredDevices(protocol::MultiSequenceSystemMatrixProtocol) + result = [AbstractDAQ] + if protocol.params.controlTx + push!(result, TxDAQController) + end + if protocol.params.saveTemperatureData + push!(result, TemperatureSensor) + end + return result +end + +function _init(protocol::MultiSequenceSystemMatrixProtocol) + if isnothing(protocol.params.sequences) + throw(IllegalStateException("Protocol requires sequences")) + end + + protocol.systemMeasState = SystemMatrixMeasState() + numPos = length(protocol.params.sequences) + measIsBGPos = [false for i = 1:numPos] + + framesPerPos = zeros(Int64, numPos) + posToIdx = zeros(Int64, numPos) + for (i, seq) in enumerate(protocol.params.sequences) + framesPerPos[i] = acqNumFrames(seq) + end + numTotalFrames = sum(framesPerPos) + posToIdx[1] = 1 + posToIdx[2:end] = cumsum(framesPerPos)[1:end-1] .+ 1 + measIsBGFrame = zeros(Bool, numTotalFrames) + + protocol.systemMeasState.measIsBGPos = measIsBGPos + protocol.systemMeasState.posToIdx = posToIdx + protocol.systemMeasState.measIsBGFrame = measIsBGFrame + protocol.systemMeasState.currPos = 1 + protocol.systemMeasState.positions = protocol.params.positions + + #Prepare Signals + numRxChannels = length(rxChannels(protocol.params.sequences[1])) # kind of hacky, but actual rxChannels for RedPitaya are only set when setupRx is called + rxNumSamplingPoints = rxNumSamplesPerPeriod(protocol.params.sequences[1]) + numPeriods = acqNumPeriodsPerFrame(protocol.params.sequences[1]) + signals = zeros(Float32, rxNumSamplingPoints, numRxChannels, numPeriods, numTotalFrames) + protocol.systemMeasState.signals = signals + + protocol.systemMeasState.currentSignal = zeros(Float32, rxNumSamplingPoints, numRxChannels, numPeriods, 1) + + + + if protocol.params.saveTemperatureData + sensor = getTemperatureSensor(protocol.scanner) + protocol.systemMeasState.temperatures = zeros(numChannels(sensor), numTotalFrames) + end + if protocol.params.controlTx + protocol.txCont = getDevice(protocol.scanner, TxDAQController) + else + protocol.txCont = nothing + end + return nothing +end + +function timeEstimate(protocol::MultiSequenceSystemMatrixProtocol) + est = "Unknown" + #if !isnothing(protocol.params.sequence) + # params = protocol.params + # seq = params.sequence + # totalFrames = (params.fgFrames + params.bgFrames) * acqNumFrameAverages(seq) + # samplesPerFrame = rxNumSamplingPoints(seq) * acqNumAverages(seq) * acqNumPeriodsPerFrame(seq) + # totalTime = (samplesPerFrame * totalFrames) / (125e6 / (txBaseFrequency(seq) / rxSamplingRate(seq))) + # time = totalTime * 1u"s" + # est = string(time) + # @show est + #end + return est +end + +function enterExecute(protocol::MultiSequenceSystemMatrixProtocol) + protocol.stopped = false + protocol.cancelled = false + protocol.finishAcknowledged = false + protocol.restored = false + protocol.systemMeasState.currPos = 1 +end + +function _execute(protocol::MultiSequenceSystemMatrixProtocol) + @debug "Measurement protocol started" + + + finished = false + notifiedStop = false + while !finished + finished = performMeasurements(protocol) + + # Stopped + notifiedStop = false + while protocol.stopped + handleEvents(protocol) + protocol.cancelled && throw(CancelException()) + if !notifiedStop + put!(protocol.biChannel, OperationSuccessfulEvent(StopEvent())) + notifiedStop = true + end + if !protocol.stopped + put!(protocol.biChannel, OperationSuccessfulEvent(ResumeEvent())) + end + sleep(0.05) + end + end + + + put!(protocol.biChannel, FinishedNotificationEvent()) + while !(protocol.finishAcknowledged) + handleEvents(protocol) + protocol.cancelled && throw(CancelException()) + end + + @info "Protocol finished." + close(protocol.biChannel) + @debug "Protocol channel closed after execution." +end + +function performMeasurements(protocol::MultiSequenceSystemMatrixProtocol) + finished = false + calib = protocol.systemMeasState + + while !finished + handleEvents(protocol) + + if protocol.stopped + enterPause(protocol) + finished = false + break + end + + wait(calib.producer) + wait(calib.consumer) + prepareDAQ(protocol) + # TODO wait time + + performMeasurement(protocol) + if protocol.systemMeasState.currPos > length(protocol.params.sequences) + calib = protocol.systemMeasState + daq = getDAQ(protocol.scanner) + wait(calib.consumer) + wait(calib.producer) + stopTx(daq) + finished = true + end + end + + return finished +end + +function enterPause(protocol::MultiSequenceSystemMatrixProtocol) + calib = protocol.systemMeasState + wait(calib.consumer) + wait(calib.producer) +end + + +function performMeasurement(protocol::MultiSequenceSystemMatrixProtocol) + # Prepare + calib = protocol.systemMeasState + index = calib.currPos + @info "Measurement" index length(calib.positions) + daq = getDAQ(protocol.scanner) + + sequence = protocol.params.sequences[index] + #if protocol.params.controlTx + # sequence = protocol.contSequence.targetSequence + #end + + channel = Channel{channelType(daq)}(32) + calib.producer = @tspawnat protocol.scanner.generalParams.producerThreadID asyncProducer(channel, daq, sequence) + bind(channel, calib.producer) + calib.consumer = @tspawnat protocol.scanner.generalParams.consumerThreadID asyncConsumer(channel, protocol, index) + while !istaskdone(calib.producer) + handleEvents(protocol) + # Dont want to throw cancel here + sleep(0.05) + end + + # Increment measured positions + calib.currPos += 1 +end + +function prepareDAQ(protocol::MultiSequenceSystemMatrixProtocol) + calib = protocol.systemMeasState + daq = getDAQ(protocol.scanner) + + sequence = protocol.params.sequences[calib.currPos] + if protocol.params.controlTx + if isnothing(protocol.contSequence) || protocol.restored || (calib.currPos == 1) + protocol.contSequence = controlTx(protocol.txCont, protocol.params.sequence) + protocol.restored = false + end + #if isempty(protocol.systemMeasState.drivefield) + # len = length(keys(protocol.contSequence.simpleChannel)) + # drivefield = zeros(ComplexF64, len, len, size(calib.signals, 3), size(calib.signals, 4)) + # calib.drivefield = mmap!(protocol, "observedField.bin", drivefield) + # applied = zeros(ComplexF64, len, len, size(calib.signals, 3), size(calib.signals, 4)) + # calib.applied = mmap!(protocol, "appliedFiled.bin", applied) + #end + sequence = protocol.contSequence + end + setup(daq, sequence) +end + +function asyncConsumer(channel::Channel, protocol::MultiSequenceSystemMatrixProtocol, index) + calib = protocol.systemMeasState + @info "readData" + daq = getDAQ(protocol.scanner) + numFrames = acqNumFrames(protocol.params.sequences[index]) + startIdx = calib.posToIdx[index] + stopIdx = startIdx + numFrames - 1 + + # Prepare Buffer + deviceBuffer = DeviceBuffer[] + if protocol.params.saveTemperatureData + tempSensor = getTemperatureSensor(protocol.scanner) + push!(deviceBuffer, TemperatureBuffer(view(calib.temperatures, :, startIdx:stopIdx), tempSensor)) + end + + sinks = StorageBuffer[] + push!(sinks, SimpleFrameBuffer(1, view(calib.signals, :, :, :, startIdx:stopIdx))) + #sequence = protocol.params.sequence + #if protocol.params.controlTx + # sequence = protocol.contSequence + # push!(sinks, DriveFieldBuffer(1, view(calib.drivefield, :, :, :, startIdx:stopIdx), sequence)) + # push!(deviceBuffer, TxDAQControllerBuffer(1, view(calib.applied, :, :, :, startIdx:stopIdx), protocol.txCont)) + #end + + sequenceBuffer = AsyncBuffer(FrameSplitterBuffer(daq, sinks), daq) + asyncConsumer(channel, sequenceBuffer, deviceBuffer) + + calib.measIsBGFrame[startIdx:stopIdx] .= calib.measIsBGPos[index] + + calib.currentSignal = calib.signals[:, :, :, stopIdx:stopIdx] + + #@info "store" + #timeStore = store(protocol, index) + #@info "done after $timeStore" +end + +function store(protocol::MultiSequenceSystemMatrixProtocol, index) + filename = file(protocol, "meta.toml") + rm(filename, force=true) + + sysObj = protocol.systemMeasState + params = MPIFiles.toDict(sysObj.positions) + params["currPos"] = index + 1 # Safely stored up to and including index + #params["stopped"] = protocol.stopped + #params["currentSignal"] = sysObj.currentSignal + params["waitTime"] = protocol.params.waitTime + params["measIsBGPos"] = sysObj.measIsBGPos + params["posToIdx"] = sysObj.posToIdx + params["measIsBGFrame"] = sysObj.measIsBGFrame + params["temperatures"] = vec(sysObj.temperatures) + params["sequence"] = toDict(protocol.params.sequence) + + open(filename, "w") do f + TOML.print(f, params) + end + + Mmap.sync!(sysObj.signals) + Mmap.sync!(sysObj.drivefield) + Mmap.sync!(sysObj.applied) + return +end + +function restore(protocol::MultiSequenceSystemMatrixProtocol) + + sysObj = protocol.systemMeasState + params = MPIFiles.toDict(sysObj.positions) + if isfile(protocol, "meta.toml") + params = TOML.parsefile(file(protocol, "meta.toml")) + sysObj.currPos = params["currPos"] + protocol.stopped = false + protocol.params.waitTime = params["waitTime"] + sysObj.measIsBGPos = params["measIsBGPos"] + sysObj.posToIdx = params["posToIdx"] + sysObj.measIsBGFrame = params["measIsBGFrame"] + temp = params["temperatures"] + if !isempty(temp) && (length(sysObj.temperatures) == length(temp)) + sysObj.temperatures[:] .= temp + end + + sysObj.positions = Positions(params) + + numBGPos = sum(sysObj.measIsBGPos) + numFGPos = length(sysObj.measIsBGPos) - numBGPos + + message = "Current position is $(sysObj.currPos). Resume from last background position instead?" + if askChoices(protocol, message, ["No", "Use"]) == 2 + temp = sysObj.currPos + while temp > 1 && !sysObj.measIsBGPos[temp] + temp = temp - 1 + end + sysObj.currPos = temp + end + + + seq = protocol.params.sequence + + storedSeq = sequenceFromDict(params["sequence"]) + if storedSeq != seq + message = "Stored sequence does not match initialized sequence. Use stored sequence instead?" + if askChoices(protocol, message, ["Cancel", "Use"]) == 1 + throw(CancelException()) + end + seq = storedSeq + protocol.params.sequence + end + + # Drive Field + if isfile(protocol, "observedField.bin") # sysObj.drivefield is still empty at point of (length(sysObj.drivefield) == length(drivefield)) + sysObj.drivefield = mmap(protocol, "observedField.bin", ComplexF64) + end + if isfile(protocol, "appliedField.bin") + sysObj.applied = mmap(protocol, "appliedField.bin", ComplexF64) + end + + + sysObj.signals = mmap(protocol, "signals.bin", Float32) + + numTotalFrames = numFGPos * protocol.params.fgFrames + protocol.params.bgFrames * numBGPos + numRxChannels = length(rxChannels(seq)) # kind of hacky, but actual rxChannels for RedPitaya are only set when setupRx is called + rxNumSamplingPoints = rxNumSamplesPerPeriod(seq) + numPeriods = acqNumPeriodsPerFrame(seq) + paramSize = (rxNumSamplingPoints, numRxChannels, numPeriods, numTotalFrames) + if size(sysObj.signals) != paramSize + throw(DimensionMismatch("Dimensions of stored signals $(size(sysObj.signals)) does not match initialized signals $paramSize")) + end + + protocol.restored = true + @info "Restored system matrix measurement" + end +end + + + +function cleanup(protocol::MultiSequenceSystemMatrixProtocol) + # NOP +end + +function stop(protocol::MultiSequenceSystemMatrixProtocol) + calib = protocol.systemMeasState + if calib.currPos <= length(calib.positions) + # OperationSuccessfulEvent is put when it actually is in the stop loop + protocol.stopped = true + else + # Stopped has no concept once all measurements are done + put!(protocol.biChannel, OperationUnsuccessfulEvent(StopEvent())) + end +end + +function resume(protocol::MultiSequenceSystemMatrixProtocol) + protocol.stopped = false + protocol.restored = true + # OperationSuccessfulEvent is put when it actually leaves the stop loop +end + +function cancel(protocol::MultiSequenceSystemMatrixProtocol) + protocol.cancelled = true # Set cancel s.t. exception can be thrown when appropiate + protocol.stopped = true # Set stop to reach a known/save state +end + + +function handleEvent(protocl::MultiSequenceSystemMatrixProtocol, event::ProgressQueryEvent) + put!(protocl.biChannel, ProgressEvent(protocl.systemMeasState.currPos, length(protocl.params.sequences), "Position", event)) +end + +function handleEvent(protocol::MultiSequenceSystemMatrixProtocol, event::DataQueryEvent) + data = nothing + if event.message == "CURR" + data = protocol.systemMeasState.currentSignal + elseif event.message == "BG" + sysObj = protocol.systemMeasState + index = sysObj.currPos + while index > 1 && !sysObj.measIsBGPos[index] + index = index - 1 + end + startIdx = sysObj.posToIdx[index] + data = copy(sysObj.signals[:, :, :, startIdx:startIdx]) + else + put!(protocol.biChannel, UnknownDataQueryEvent(event)) + end + put!(protocol.biChannel, DataAnswerEvent(data, event)) +end + +function handleEvent(protocol::MultiSequenceSystemMatrixProtocol, event::DatasetStoreStorageRequestEvent) + if false + # TODO this should be some sort of storage failure event + put!(protocol.biChannel, IllegaleStateEvent("Calibration measurement is not done yet. Cannot save!")) + else + store = event.datastore + scanner = protocol.scanner + mdf = event.mdf + data = protocol.systemMeasState.signals + positions = protocol.systemMeasState.positions + isBackgroundFrame = protocol.systemMeasState.measIsBGFrame + temperatures = nothing + if protocol.params.saveTemperatureData + temperatures = protocol.systemMeasState.temperatures + end + drivefield = nothing + if !isempty(protocol.systemMeasState.drivefield) + drivefield = protocol.systemMeasState.drivefield + end + applied = nothing + if !isempty(protocol.systemMeasState.applied) + applied = protocol.systemMeasState.applied + end + filename = saveasMDF(store, scanner, protocol.params.sequences[1], data, positions, isBackgroundFrame, mdf; storeAsSystemMatrix=protocol.params.saveAsSystemMatrix, temperatures=temperatures, drivefield=drivefield, applied=applied) + @show filename + put!(protocol.biChannel, StorageSuccessEvent(filename)) + end +end + +handleEvent(protocol::MultiSequenceSystemMatrixProtocol, event::FinishedAckEvent) = protocol.finishAcknowledged = true + +protocolInteractivity(protocol::MultiSequenceSystemMatrixProtocol) = Interactive() +protocolMDFStudyUse(protocol::MultiSequenceSystemMatrixProtocol) = UsingMDFStudy() diff --git a/src/Protocols/Protocol.jl b/src/Protocols/Protocol.jl index 5b4a17af..9fe02129 100644 --- a/src/Protocols/Protocol.jl +++ b/src/Protocols/Protocol.jl @@ -312,4 +312,5 @@ include("MPIForceProtocol.jl") include("RobotMPIMeasurementProtocol.jl") include("RobotBasedProtocol.jl") include("ContinousMeasurementProtocol.jl") +include("MultiSequenceSystemMatrixProtocol.jl") #include("TransferFunctionProtocol.jl") \ No newline at end of file From 129fe0bc681b0a28edf8d4b0820cd8149bb0311d Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Tue, 29 Aug 2023 15:29:55 +0200 Subject: [PATCH 021/168] fixes while debugging old controller, working --- src/Devices/DAQ/DAQ.jl | 8 ++-- src/Devices/Virtual/TxDAQController.jl | 56 ++++++++++++++------------ 2 files changed, 36 insertions(+), 28 deletions(-) diff --git a/src/Devices/DAQ/DAQ.jl b/src/Devices/DAQ/DAQ.jl index 6a75da5b..4e9812aa 100644 --- a/src/Devices/DAQ/DAQ.jl +++ b/src/Devices/DAQ/DAQ.jl @@ -239,9 +239,11 @@ function applyForwardCalibration!(seq::Sequence, daq::AbstractDAQ) # TODO/JA: what about the other types of channels? for channel in periodicElectricalTxChannels(seq) - - offsetVolts = offset(channel)*calibration(daq, id(channel))(0) # use DC value for offsets - offset!(channel, uconvert(u"V",abs(offsetVolts))) + off = offset(channel) + if dimension(off) != dimension(1.0u"V") + offsetVolts = off*calibration(daq, id(channel))(0) # use DC value for offsets + offset!(channel, uconvert(u"V",abs(offsetVolts))) + end for comp in periodicElectricalComponents(channel) amp = amplitude(comp) diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index a3ecb5a2..c376ad8b 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -65,24 +65,27 @@ function ControlSequence(txCont::TxDAQController, target::Sequence, daq::Abstrac applyForwardCalibration!(currSeq, daq) # uses the forward calibration to convert the values for the field from T to V seqControlledChannels = getControlledChannels(currSeq) + + @debug "ControlSequence" seqControlledChannels # Dict(PeriodicElectricalChannel => TxChannelParams) controlledChannelsDict = createControlledChannelsDict(seqControlledChannels, daq) # should this be changed to components instead of channels? refIndices = createReferenceIndexMapping(controlledChannelsDict, daq) controlSequenceType = decideControlSequenceType(target) + @debug "ControlSequence: Decided on using ControlSequence of type" controlSequenceType if controlSequenceType==CrossCouplingControlSequence # temporarily remove first (and only) component from each channel - comps = [popfirst!(periodicElectricalComponents(channel)) for channel in periodicElectricalTxChannels(fields(currSeq)[1])] + comps = [popfirst!(channel.components) for channel in periodicElectricalTxChannels(fields(currSeq)[1])] # insert all components into each channel in the same order, all comps from the other channels are set to 0T for (i, channel) in enumerate(periodicElectricalTxChannels(fields(currSeq)[1])) for (j, comp) in enumerate(comps) copy_ = deepcopy(comp) if i!=j - amplitude!(copy_, 0.0u"T") + amplitude!(copy_, 0.0u"V") end push!(channel.components, copy_) end @@ -112,6 +115,7 @@ function decideControlSequenceType(target::Sequence) moreThanThreeChannels = length(getControlledChannels(target)) > 3 moreThanOneField = length(getControlledFields(target)) > 1 needsDecoupling_ = needsDecoupling(target) + @debug "decideControlSequenceType:" hasAWComponent moreThanOneComponent moreThanThreeChannels moreThanOneField needsDecoupling_ if needsDecoupling_ && !hasAWComponent && !moreThanOneField && !moreThanThreeChannels && !moreThanOneComponent return CrossCouplingControlSequence @@ -180,7 +184,7 @@ function createLUTs(seqChannel::Vector{PeriodicElectricalChannel}, seq::Sequence N = rxNumSamplingPoints(seq) D = length(seqChannel) - dfCyclesPerPeriod = Int[lcm(dfDivider(seq))/divider(components(chan)[i]) for (i,chan) in enumerate(seqChan)] + dfCyclesPerPeriod = Int[lcm(dfDivider(seq))/divider(components(chan)[i]) for (i,chan) in enumerate(seqChannel)] sinLUT = zeros(D,N) cosLUT = zeros(D,N) @@ -242,7 +246,7 @@ function prepareSequenceForControl(seq::Sequence) end end contField = MagneticField(;id = _id, channels = periodicChannel, safeStartInterval = safeStart, safeTransitionInterval = safeTrans, - safeEndInterval = safeEnd, safeErrorInterval = safeError, decouple = false, control = false) + safeEndInterval = safeEnd, safeErrorInterval = safeError, decouple = decouple(field), control = true) push!(_fields, contField) end end @@ -282,7 +286,7 @@ function controlTx(txCont::TxDAQController, seq::Sequence, ::Nothing = nothing) daq = dependency(txCont, AbstractDAQ) setupRx(daq, seq) control = ControlSequence(txCont, seq, daq) # depending on the controlled channels and settings this will select the appropiate type of ControlSequence - return controlTx(txCont, seq, control) + return controlTx(txCont, control) else @warn "The sequence you selected does not need control, even though the protocol wanted to control!" return seq @@ -429,9 +433,9 @@ function getControlResult(cont::ControlSequence)::Sequence # Use the magnetic field that are controlled from currSeq and all uncontrolled fields and general settings from target - _name = "Control Result for target $(name(cont.target))" - general = GeneralSettings(;name=_name, description = descrption(cont.target), targetScanner = targetScanner(cont.target), baseFrequency = baseFrequency(cont.target)) - acq = cont.target.acquisition + _name = "Control Result for target $(name(cont.targetSequence))" + general = GeneralSettings(;name=_name, description = description(cont.targetSequence), targetScanner = targetScanner(cont.targetSequence), baseFrequency = baseFrequency(cont.target)) + acq = cont.targetSequence.acquisition _fields = MagneticField[] for field in fields(cont.currSequence) @@ -445,7 +449,7 @@ function getControlResult(cont::ControlSequence)::Sequence safeEndInterval = safeEnd, safeErrorInterval = safeError, decouple = false, control = false) push!(_fields, contField) end - for field in fields(cont.target) + for field in fields(cont.targetSequence) if !control(field) push!(_fields, field) end @@ -516,6 +520,7 @@ function updateControl!(cont::ControlSequence, txCont::TxDAQController, Γ::Matr @debug "Updating control values" κ = calcControlMatrix(cont) newTx = updateControlMatrix(cont, txCont, Γ, Ω, κ) + if checkFieldToVolt(κ, Γ, cont, txCont) && checkVoltLimits(newTx, cont) updateControlSequence!(cont, newTx) return true @@ -529,16 +534,16 @@ end # Ω: Desired Matrix # κ: Last Set Matrix function updateControlMatrix(cont::CrossCouplingControlSequence, txCont::TxDAQController, Γ::Matrix{<:Complex}, Ω::Matrix{<:Complex}, κ::Matrix{<:Complex}) - if needsDecoupling(cont.target) + if needsDecoupling(cont.targetSequence) β = Γ*inv(κ) else β = diagm(diag(Γ))*inv(diagm(diag(κ))) end newTx = inv(β)*Ω - @debug "Last matrix:" κ - @debug "Ref matrix" Γ - @debug "Desired matrix" Ω - @debug "New matrix" newTx + @debug "Last TX matrix [V]:" κ + @debug "Ref matrix [T]:" Γ + @debug "Desired matrix [T]:" Ω + @debug "New TX matrix [V]:" newTx return newTx end @@ -547,10 +552,10 @@ function updateControlMatrix(cont::AWControlSequence, txCont::TxDAQController, # The problem is, that to achieve 0 we will always output zero, but we would need a much more sophisticated method to solve this newTx = κ./Γ.*Ω - @debug "Last matrix:" κ - @debug "Ref matrix" Γ - @debug "Desired matrix" Ω - @debug "New matrix" newTx + @debug "Last TX matrix [V]:" κ + @debug "Ref matrix [T]:" Γ + @debug "Desired matrix [V]:" Ω + @debug "New TX matrix [T]:" newTx return newTx end @@ -571,7 +576,7 @@ end function calcFieldsFromRef(cont::CrossCouplingControlSequence, uRef::Array{Float32, 4}) len = numControlledChannels(cont) N = rxNumSamplingPoints(cont.currSequence) - dividers = Int64[divider.(getPrimaryComponents(cont))] + dividers = divider.(getPrimaryComponents(cont)) frequencies = ustrip(u"Hz", txBaseFrequency(cont.currSequence)) ./ dividers Γ = zeros(ComplexF64, len, len, size(uRef, 3), size(uRef, 4)) @@ -647,7 +652,7 @@ function calcDesiredField(cont::CrossCouplingControlSequence) desiredField = zeros(ComplexF64, controlMatrixShape(cont)) for (i, channel) in enumerate(getControlledChannels(cont.targetSequence)) comp = components(channel)[1] - desiredField[i, i] = ustrip(u"V", amplitude(comp)) * exp(im*ustrip(u"rad", phase(comp))) + desiredField[i, i] = ustrip(u"T", amplitude(comp)) * exp(im*ustrip(u"rad", phase(comp))) end return desiredField end @@ -744,17 +749,18 @@ end function checkFieldToVolt(oldTx::Matrix{<:Complex}, Γ::Matrix{<:Complex}, cont::CrossCouplingControlSequence, txCont::TxDAQController) - dividers = Int64[divider.(getPrimaryComponents(cont))] + dividers = divider.(getPrimaryComponents(cont)) frequencies = ustrip(u"Hz", txBaseFrequency(cont.currSequence)) ./ dividers calibFieldToVoltEstimate = [ustrip(u"V/T", chan.calibration(frequencies[i])) for (i,chan) in enumerate(getControlledDAQChannels(cont))] calibFieldToVoltMeasured = (diag(oldTx) ./ diag(Γ)) - abs_deviation = 1.0 .- abs.(calibFieldToVoltMeasured./calibFieldToVoltEstimate) + abs_deviation = abs.(1.0 .- calibFieldToVoltMeasured./calibFieldToVoltEstimate) phase_deviation = angle.(calibFieldToVoltMeasured./calibFieldToVoltEstimate) - @debug "We expected $(calibFieldToVoltEstimate) and got $(calibFieldToVoltMeasured), deviation: $abs_deviation" + @debug "checkFieldToVolt: We expected $(calibFieldToVoltEstimate) V/T and got $(calibFieldToVoltMeasured) V/T, deviation: $(abs_deviation*100) %" valid = maximum( abs_deviation ) < txCont.params.fieldToVoltDeviation + if !valid - @warn "Measured field to volt deviates by $abs_deviation from estimate, exceeding allowed deviation" + @warn "Measured field to volt deviates by $(abs_deviation*100) % from estimate, exceeding allowed deviation of $(txCont.params.fieldToVoltDeviation*100) %" elseif maximum(abs.(phase_deviation)) > 10/180*pi @warn "The phase of the measured field to volt deviates by $phase_deviation from estimate. Please check you phases! Continuing anyways..." end @@ -767,7 +773,7 @@ function checkFieldToVolt(oldTx::Matrix{<:Complex}, Γ::Matrix{<:Complex}, cont: calibFieldToVoltEstimate = reduce(vcat,[ustrip.(u"V/T", chan.calibration(frequencies)) for chan in getControlledDAQChannels(cont)]') calibFieldToVoltMeasured = oldTx ./ Γ - abs_deviation = 1.0 .- abs.(calibFieldToVoltMeasured[cont.rfftIndices,:]./calibFieldToVoltEstimate[cont.rfftIndices,:]) # TODO/JA: fix indicies!!! + abs_deviation = abs.(1.0 .- calibFieldToVoltMeasured[cont.rfftIndices,:]./calibFieldToVoltEstimate[cont.rfftIndices,:]) # TODO/JA: fix indicies!!! phase_deviation = angle.(calibFieldToVoltMeasured[cont.rfftIndices,:]./calibFieldToVoltEstimate[cont.rfftIndices,:]) @debug "We expected $(calibFieldToVoltEstimate) and got $(calibFieldToVoltMeasured), deviation: $abs_deviation" valid = maximum( abs_deviation ) < txCont.params.fieldToVoltDeviation From ad025309e95151a95f1dc3e056b1123c29cea1cc Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Tue, 29 Aug 2023 15:30:36 +0200 Subject: [PATCH 022/168] improvement of accuracy definition --- src/Devices/Virtual/TxDAQController.jl | 47 +++++++++++++++++++------- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index c376ad8b..82f2988f 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -1,9 +1,10 @@ export TxDAQControllerParams, TxDAQController, controlTx Base.@kwdef mutable struct TxDAQControllerParams <: DeviceParams - phaseAccuracy::Float64 - amplitudeAccuracy::Float64 + phaseAccuracy::typeof(1.0u"rad") + relativeAmplitudeAccuracy::Float64 controlPause::Float64 + absoluteAmplitudeAccuracy::typeof(1.0u"T") = 50.0u"µT" maxControlSteps::Int64 = 20 fieldToVoltDeviation::Float64 = 0.2 controlDC::Bool = false @@ -349,6 +350,16 @@ function controlTx(txCont::TxDAQController, control::ControlSequence) @info "CONTROL STEP $i" # Prepare control measurement setup(daq, control.currSequence) + + menu = REPL.TerminalMenus.RadioMenu(["Continue", "Abort"], pagesize=2) + choice = REPL.TerminalMenus.request("Please confirm the current values for control:", menu) + if choice == 1 + println("Continuing...") + else + println("Control cancelled") + error("Control cancelled!") + end + channel = Channel{channelType(daq)}(32) buffer = AsyncBuffer(FrameSplitterBuffer(daq, StorageBuffer[DriveFieldBuffer(1, zeros(ComplexF64, controlMatrixShape(control)..., 1, 1), control)]), daq) @info "Control measurement started" @@ -502,16 +513,28 @@ end #checkFieldDeviation(cont::ControlSequence, txCont::TxDAQController, uRef) = checkFieldDeviation(cont, txCont, calcFieldFromRef(cont, uRef)) checkFieldDeviation(cont::ControlSequence, txCont::TxDAQController, Γ::Matrix{<:Complex}) = checkFieldDeviation(cont, txCont, Γ, calcDesiredField(cont)) function checkFieldDeviation(cont::ControlSequence, txCont::TxDAQController, Γ::Matrix{<:Complex}, Ω::Matrix{<:Complex}) - if correct_coupling || isa(cont, AWControlSequence) - diff = Ω - Γ - else - diff = diagm(diag(Ω)) - diagm(diag(Γ)) - end - deviation = maximum(abs.(diff)) / maximum(abs.(Ω)) - @debug "Check field deviation" Ω Γ - @debug "Ω - Γ = " diff - @info "deviation = $(deviation) allowed= $(txCont.params.amplitudeAccuracy)" - return deviation < txCont.params.amplitudeAccuracy + + diff = Ω - Γ + abs_deviation = abs.(diff) + rel_deviation = abs_deviation ./ abs.(Ω) + rel_deviation[abs.(Ω).==0] .= 0 # relative deviation does not make sense for a zero goal + phase_deviation = angle.(Ω).-angle.(Γ) + phase_deviation[abs.(Ω).==0] .= 0 # phase deviation does not make sense for a zero goal + + if !needsDecoupling(cont.targetSequence) && !isa(cont,AWControlSequence) + abs_deviation = diag(abs_deviation) + rel_deviation = diag(rel_deviation) + phase_deviation = diag(phase_deviation) + elseif isa(cont, AWControlSequence) + # TODO/JA: select only components that are needed + end + @debug "Check field deviation [T]" Ω Γ + @debug "Ω - Γ = " abs_deviation rel_deviation phase_deviation + @info "Observed field deviation:\n\t$(abs_deviation) T\n\t$(rel_deviation*100) %\n\t$(phase_deviation/pi*180)°\n allowed: $(txCont.params.absoluteAmplitudeAccuracy), $(txCont.params.relativeAmplitudeAccuracy*100) %, $(uconvert(u"°",txCont.params.phaseAccuracy))" + phase_ok = abs.(phase_deviation) .< ustrip(u"rad", txCont.params.phaseAccuracy) + amplitude_ok = (abs.(abs_deviation) .< ustrip(u"T", txCont.params.absoluteAmplitudeAccuracy)) .| (abs.(rel_deviation) .< txCont.params.relativeAmplitudeAccuracy) + @debug "Field deviation:" amplitude_ok phase_ok + return all(phase_ok) && all(amplitude_ok) end From fa1e6e0ca3128866c48debb6adcaceef68c9c009 Mon Sep 17 00:00:00 2001 From: Justin Ackers Date: Thu, 31 Aug 2023 09:31:21 +0200 Subject: [PATCH 023/168] extended ArbitraryElectricalComponent added amplitude and phase and implemented loading from h5 files --- src/Devices/DAQ/DAQ.jl | 23 +++--- src/Devices/DAQ/RedPitayaDAQ.jl | 2 +- src/Devices/Virtual/TxDAQController.jl | 15 ++-- src/Sequences/PeriodicElectricalChannel.jl | 83 +++++++++++++--------- 4 files changed, 65 insertions(+), 58 deletions(-) diff --git a/src/Devices/DAQ/DAQ.jl b/src/Devices/DAQ/DAQ.jl index 4e9812aa..a4ffea63 100644 --- a/src/Devices/DAQ/DAQ.jl +++ b/src/Devices/DAQ/DAQ.jl @@ -245,27 +245,22 @@ function applyForwardCalibration!(seq::Sequence, daq::AbstractDAQ) offset!(channel, uconvert(u"V",abs(offsetVolts))) end - for comp in periodicElectricalComponents(channel) + for comp in components(channel) amp = amplitude(comp) pha = phase(comp) if dimension(amp) != dimension(1.0u"V") f_comp = ustrip(u"Hz", txBaseFrequency(seq)) / divider(comp) complex_comp = (amp*exp(im*pha)) * calibration(daq, id(channel))(f_comp) - amplitude!(comp,uconvert(u"V",abs(complex_comp))) - phase!(comp,angle(complex_comp)u"rad") + amplitude!(comp, uconvert(u"V",abs(complex_comp))) + phase!(comp, angle(complex_comp)u"rad") + if comp isa ArbitraryElectricalComponent + f_awg = rfftfreq(2^14, f_comp*2^14) + calib = calibration(daq, name)(f_awg) ./ calibration(daq, name)(f_comp) # since amplitude and phase are already calibrated for the base frequency, here we need to remove that factor + values!(aw_comp, irfft(rfft(wave).*calib, 2^14)) + end end + end - - for aw_comp in arbitraryElectricalComponents(channel) - wave = values(aw_comp) - if dimension(wave[1]) != dimension(1.0u"V") - f_comp = ustrip(u"Hz", txBaseFrequency(seq)) / divider(comp) - f_awg = rfftfreq(2^14, f_comp*2^14) - wave = irfft(rfft(wave).*calibration(daq, name)(f_awg), 2^14) - values!(aw_comp, wave) - end - - end end nothing end diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index dd997bd7..563efedf 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -687,7 +687,7 @@ function prepareTx(daq::RedPitayaDAQ, sequence::Sequence) # Length check < 1 happens in setupTx already comps = arbitraryElectricalComponents(channel) if length(comps)==1 - wave = values(comps[1]) + wave = scaledValues(comps[1]) end allAwg[name] = wave diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index 82f2988f..c69f78f8 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -235,16 +235,11 @@ function prepareSequenceForControl(seq::Sequence) periodicChannel = [deepcopy(channel) for channel in periodicElectricalTxChannels(field)] #periodicComponents = [comp for channel in periodicChannel for comp in periodicElectricalComponents(channel)] for channel in periodicChannel - for comp in periodicElectricalComponents(channel) + for comp in components(channel) if dimension(amplitude(comp)) != dimension(1.0u"T") error("The amplitude components of a field that is controlled by a TxDAQController need to be given in T. Please fix component $(id(comp)) of channel $(id(channel))") end end - for comp in arbitraryElectricalComponents(channel) - if dimension(values(comp)[1]) != dimension(1.0u"T") - error("The waveform components of a field that is controlled by a TxDAQController need to be given in T. Please fix component $(id(comp)) of channel $(id(channel))") - end - end end contField = MagneticField(;id = _id, channels = periodicChannel, safeStartInterval = safeStart, safeTransitionInterval = safeTrans, safeEndInterval = safeEnd, safeErrorInterval = safeError, decouple = decouple(field), control = true) @@ -700,7 +695,7 @@ function calcDesiredField(cont::AWControlSequence) end desiredField[i, cont.rfftIndices[i,j,:]] .= ustrip(u"T",amplitude(comp)) * exp(im*ustrip(u"rad",phase(comp)-pi/2)) # The phase given in the component is for a sine, but the FFT-phase uses a cosine elseif isa(comp, ArbitraryElectricalComponent) - desiredField[i, cont.rfftIndices[i,j,:]] .= rfft(ustrip.(u"T",values(comp)))[2:sum(cont.rfftIndices[i,j,:])+1]/(0.5*2^14) # the buffer length should always be 2^14 currently + desiredField[i, cont.rfftIndices[i,j,:]] .= rfft(ustrip.(u"T",scaledValues(comp)))[2:sum(cont.rfftIndices[i,j,:])+1]/(0.5*2^14) # the buffer length should always be 2^14 currently end end end @@ -729,7 +724,7 @@ function calcControlMatrix(cont::AWControlSequence) if isa(comp, PeriodicElectricalComponent) oldTx[i, cont.rfftIndices[i,j,:]] .= ustrip(u"V",amplitude(comp)) * exp(im*ustrip(u"rad",phase(comp)-pi/2)) # The phase given in the component is for a sine, but the FFT-phase uses a cosine elseif isa(comp, ArbitraryElectricalComponent) - oldTx[i, cont.rfftIndices[i,j,:]] .= rfft(ustrip.(u"V",values(comp)))[2:sum(cont.rfftIndices[i,j,:])+1]/(0.5*2^14) # the buffer length should always be 2^14 currently + oldTx[i, cont.rfftIndices[i,j,:]] .= rfft(ustrip.(u"V",scaledValues(comp)))[2:sum(cont.rfftIndices[i,j,:])+1]/(0.5*2^14) # the buffer length should always be 2^14 currently end end end @@ -759,7 +754,9 @@ function updateControlSequence!(cont::AWControlSequence, newTx::Matrix) elseif isa(comp, ArbitraryElectricalComponent) spectrum = zeros(ComplexF64, 2^13+1) spectrum[2:sum(cont.rfftIndices[i,j,:])+1] .= newTx[i, cont.rfftIndices[i,j,:]] - values!(comp, irfft(spectrum, 2^14)*(0.5*2^14)*u"V") + amplitude!(comp, 1.0u"V") + phase!(comp, 0.0u"rad") + values!(comp, irfft(spectrum, 2^14)*(0.5*2^14)) end end end diff --git a/src/Sequences/PeriodicElectricalChannel.jl b/src/Sequences/PeriodicElectricalChannel.jl index 1fd624c4..ab78de02 100644 --- a/src/Sequences/PeriodicElectricalChannel.jl +++ b/src/Sequences/PeriodicElectricalChannel.jl @@ -31,8 +31,12 @@ Base.@kwdef mutable struct ArbitraryElectricalComponent <: ElectricalComponent id::AbstractString "Divider of the component." divider::Integer - "Values for the waveform of the component" - values::Union{Vector{typeof(1.0u"T")}, Vector{typeof(1.0u"A")}, Vector{typeof(1.0u"V")}} + "Amplitude scale of the base waveform for each period of the field" + amplitude::Union{Vector{typeof(1.0u"T")}, Vector{typeof(1.0u"A")}, Vector{typeof(1.0u"V")}} + "Phase of the component for each period of the field." + phase::Vector{typeof(1.0u"rad")} + "Values for the base waveform of the component, will be multiplied by `amplitude`" + values::Vector{Float64} end """Electrical channel based on based on periodic base functions. Only the @@ -101,8 +105,42 @@ function createChannelComponent(componentID::AbstractString, componentDict::Dict end function createChannelComponent(componentID::AbstractString, ::Type{PeriodicElectricalComponent}, componentDict::Dict{String, Any}) - divider = componentDict["divider"] + + divider, amplitude, phase = extractBasicComponentProperties(componentDict) + + if haskey(componentDict, "waveform") + waveform = toWaveform(componentDict["waveform"]) + else + waveform = WAVEFORM_SINE # Default to sine + end + + return PeriodicElectricalComponent(id=componentID, divider=divider, amplitude=amplitude, phase=phase, waveform=waveform) +end + +function createChannelComponent(componentID::AbstractString, ::Type{ArbitraryElectricalComponent}, componentDict::Dict{String, Any}) + divider, amplitude, phase = extractBasicComponentProperties(componentDict) + + if componentDict["values"] isa AbstractString # case 1: filename to waveform + filename = joinpath(homedir(), ".mpi", "Waveforms", componentDict["values"]) + try + values = h5read(filename, "/values") + catch + throw(SequenceConfigurationError("Could not load the waveform $(componentDict["values"]), either the file does not exist at $filename or the file structure is wrong")) + end + else # case 2: vector of real numbers + values = componentDict["values"] + end + + if abs(values[1]-values[end])>0.001 # is the jump limit of 0.1% too strict? + throw(SequenceConfigurationError("The first and last value of a waveform should be close enough together to not produce a jump! Please check your waveform")) + end + + return ArbitraryElectricalComponent(id=componentID, divider=divider,amplitude=amplitude, phase=phase, values=values) +end + +function extractBasicComponentProperties(componentDict::Dict{String, Any}) + divider = componentDict["divider"] amplitude = uparse.(componentDict["amplitude"]) if eltype(amplitude) <: Unitful.Current amplitude = amplitude .|> u"A" @@ -131,28 +169,7 @@ function createChannelComponent(componentID::AbstractString, ::Type{PeriodicElec else phase = fill(0.0u"rad", length(divider)) # Default phase end - - if haskey(componentDict, "waveform") - waveform = toWaveform(componentDict["waveform"]) - else - waveform = WAVEFORM_SINE # Default to sine - end - return PeriodicElectricalComponent(id=componentID, divider=divider, amplitude=amplitude, phase=phase, waveform=waveform) -end - -function createChannelComponent(componentID::AbstractString, ::Type{ArbitraryElectricalComponent}, componentDict::Dict{String, Any}) - divider = componentDict["divider"] - values = uparse.(componentDict["values"]) - if eltype(values) <: Unitful.Current - values = values .|> u"A" - elseif eltype(values) <: Unitful.Voltage - values = values .|> u"V" - elseif eltype(values) <: Unitful.BField - values = values .|> u"T" - else - error("The values have to be either given as a current or in tesla. You supplied the type `$(eltype(values))`.") - end - return ArbitraryElectricalComponent(id=componentID, divider=divider, values=values) + return divider, amplitude, phase end export offset, offset! @@ -186,8 +203,8 @@ divider(component::ElectricalComponent, trigger::Integer=1) = length(component.d divider!(component::PeriodicElectricalComponent,value::Integer) = component.divider = value export amplitude, amplitude! -amplitude(component::PeriodicElectricalComponent; period::Integer=1) = component.amplitude[period] -function amplitude!(component::PeriodicElectricalComponent, value::Union{typeof(1.0u"T"),typeof(1.0u"V")}; period::Integer=1) +amplitude(component::Union{PeriodicElectricalComponent,ArbitraryElectricalComponent}; period::Integer=1) = component.amplitude[period] +function amplitude!(component::Union{PeriodicElectricalComponent,ArbitraryElectricalComponent}, value::Union{typeof(1.0u"T"),typeof(1.0u"V"),typeof(1.0u"A")}; period::Integer=1) if eltype(component.amplitude) != typeof(value) && length(component.amplitude) == 1 component.amplitude = typeof(value)[value] else @@ -195,19 +212,17 @@ function amplitude!(component::PeriodicElectricalComponent, value::Union{typeof( end end amplitude(component::SweepElectricalComponent; trigger::Integer=1) = component.amplitude[period] -amplitude(component::ArbitraryElectricalComponent) = maximum(abs.(component.values)) -amplitude!(component::ArbitraryElectricalComponent, values) = error("Can not change the amplitude of an ArbitraryElectricalComponent. Use values!() to change the waveform.") export phase, phase! -phase(component::PeriodicElectricalComponent, trigger::Integer=1) = component.phase[trigger] -phase!(component::PeriodicElectricalComponent, value::typeof(1.0u"rad"); period::Integer=1) = component.phase[period] = value +phase(component::Union{PeriodicElectricalComponent,ArbitraryElectricalComponent}, trigger::Integer=1) = component.phase[trigger] +phase!(component::Union{PeriodicElectricalComponent,ArbitraryElectricalComponent}, value::typeof(1.0u"rad"); period::Integer=1) = component.phase[period] = value phase(component::SweepElectricalComponent, trigger::Integer=1) = 0.0u"rad" -phase!(component::ArbitraryElectricalComponent, value::typeof(1.0u"rad"); period::Integer=1) = error("Can not change the phase of an ArbitraryElectricalComponent. Use values!() to change the waveform.") -phase(component::ArbitraryElectricalComponent, trigger::Integer=1) = 0.0u"rad" export values, values! values(component::ArbitraryElectricalComponent) = component.values -values!(component::ArbitraryElectricalComponent, values::Union{Vector{typeof(1.0u"T")}, Vector{typeof(1.0u"A")}, Vector{typeof(1.0u"V")}}) = component.values = values +values!(component::ArbitraryElectricalComponent, values::Vector{Float64}) = component.values = values +scaledValues(component::ArbitraryElectricalComponent) = amplitude(component) .* circshift(values(component), round(Int,phase(component)/(2π*u"rad")*length(values(component)))) + export waveform, waveform! waveform(component::ElectricalComponent) = component.waveform From 9645043033236f4b380ccd6391bf8dc491e6aea9 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Thu, 31 Aug 2023 14:37:41 +0200 Subject: [PATCH 024/168] small improvements --- Project.toml | 1 + src/Devices/DAQ/DAQ.jl | 7 ++-- src/Devices/DAQ/RedPitayaDAQ.jl | 12 +++--- src/Devices/Virtual/TxDAQController.jl | 56 ++++++++++++++------------ src/MPIMeasurements.jl | 1 + src/Scanner.jl | 2 +- 6 files changed, 44 insertions(+), 35 deletions(-) diff --git a/Project.toml b/Project.toml index 66ebd487..de466832 100644 --- a/Project.toml +++ b/Project.toml @@ -24,6 +24,7 @@ StringEncodings = "69024149-9ee7-55f6-a4c4-859efe599b68" TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76" ThreadPools = "b189fb0b-2eb5-4ed4-bc0c-d34c51242431" UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" +UnicodePlots = "b8865327-cd53-5732-bb35-84acbb429228" Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" [compat] diff --git a/src/Devices/DAQ/DAQ.jl b/src/Devices/DAQ/DAQ.jl index a4ffea63..a271546a 100644 --- a/src/Devices/DAQ/DAQ.jl +++ b/src/Devices/DAQ/DAQ.jl @@ -254,9 +254,10 @@ function applyForwardCalibration!(seq::Sequence, daq::AbstractDAQ) amplitude!(comp, uconvert(u"V",abs(complex_comp))) phase!(comp, angle(complex_comp)u"rad") if comp isa ArbitraryElectricalComponent - f_awg = rfftfreq(2^14, f_comp*2^14) - calib = calibration(daq, name)(f_awg) ./ calibration(daq, name)(f_comp) # since amplitude and phase are already calibrated for the base frequency, here we need to remove that factor - values!(aw_comp, irfft(rfft(wave).*calib, 2^14)) + N = length(values(comp)) + f_awg = rfftfreq(N, f_comp*N) + calib = calibration(daq, id(channel))(f_awg) ./ calibration(daq, id(channel))(f_comp) # since amplitude and phase are already calibrated for the base frequency, here we need to remove that factor + values!(comp, irfft(rfft(values(comp)).*calib, N)) end end diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index 563efedf..aba27a05 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -434,7 +434,7 @@ function startProducer(channel::Channel, daq::RedPitayaDAQ, numFrames) # Start pipeline @debug "Pipeline started" try - @debug currentWP(daq.rpc) + @debug "Producer:" currentWP(daq.rpc) readSamples(rpu, startSample, samplesToRead, channel, chunkSize = chunkSize) catch e @info "Attempting reconnect to reset pipeline" @@ -561,7 +561,7 @@ function setupTxComponent!(batch::ScpiBatch, daq::RedPitayaDAQ, channel::Periodi waveform_ = uppercase(fromWaveform(waveform(component))) if !isWaveformAllowed(daq, id(channel), waveform(component)) throw(SequenceConfigurationError("The channel with the ID `$(id(channel))` "* - "defines a waveforms of $waveform_, but the scanner channel does not allow this.")) + "defines a waveform of $waveform_, but the scanner channel does not allow this.")) end @add_batch batch signalTypeDAC!(daq.rpc, channelIdx_, componentIdx, waveform_) end @@ -573,7 +573,7 @@ function setupTxComponent!(batch::ScpiBatch, daq::RedPitayaDAQ, channel::Periodi waveform_ = uppercase(fromWaveform(waveform(component))) if !isWaveformAllowed(daq, id(channel), waveform(component)) throw(SequenceConfigurationError("The channel with the ID `$(id(channel))` "* - "defines a waveforms of $waveform_, but the scanner channel does not allow this.")) + "defines a waveform of $waveform_, but the scanner channel does not allow this.")) end # Waveform is set together with amplitudes for arbitrary waveforms end @@ -617,7 +617,7 @@ function setupRx(daq::RedPitayaDAQ, sequence::Sequence) rxIDs = sort(union(channelIdx(daq, daq.rxChanIDs), channelIdx(daq, daq.refChanIDs))) selection = [false for i = 1:length(daq.rpc)] for i in map(x->div(x -1, 2) + 1, rxIDs) - @debug i + @debug "setupRx: Adding RP id $i to cluster view" selection[i] = true end daq.rpv = RedPitayaClusterView(daq.rpc, selection) @@ -669,7 +669,6 @@ end function prepareTx(daq::RedPitayaDAQ, sequence::Sequence) stopTx(daq) - @debug "Preparing amplitude and phase" allAmps = Dict{String, Vector{typeof(1.0u"V")}}() allPhases = Dict{String, Vector{typeof(1.0u"rad")}}() allAwg = Dict{String, Vector{typeof(1.0u"V")}}() @@ -694,7 +693,8 @@ function prepareTx(daq::RedPitayaDAQ, sequence::Sequence) allAmps[name] = amps allPhases[name] = phases end - @debug "prepareTx: Outputting the following amplitudes and phases:" allAmps allPhases allAwg + @debug "prepareTx: Outputting the following amplitudes and phases:" allAmps allPhases awg=lineplot(1:2^14,ustrip.(u"V",hcat(collect(Base.values(allAwg))...)),ylabel="AWG / V", name=collect(Base.keys(allAwg)), canvas=DotCanvas, border=:ascii, height=10, xlim=(1,2^14)) + setTxParams(daq, allAmps, allPhases, allAwg) end diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index c69f78f8..23c3d365 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -74,7 +74,7 @@ function ControlSequence(txCont::TxDAQController, target::Sequence, daq::Abstrac refIndices = createReferenceIndexMapping(controlledChannelsDict, daq) controlSequenceType = decideControlSequenceType(target) - @debug "ControlSequence: Decided on using ControlSequence of type" controlSequenceType + @debug "ControlSequence: Decided on using ControlSequence of type $controlSequenceType" if controlSequenceType==CrossCouplingControlSequence @@ -99,7 +99,7 @@ function ControlSequence(txCont::TxDAQController, target::Sequence, daq::Abstrac elseif controlSequenceType == AWControlSequence # use the new controller # AW offset will get ignored -> should be zero - if any(x->any(mean.(values.(arbitraryElectricalComponents(x))).>1u"µT"), seqControlledChannels) + if any(x->any(mean.(scaledValues.(arbitraryElectricalComponents(x))).>1u"µT"), getControlledChannels(target)) error("The DC-component of arbitrary waveform components cannot be handled during control! Please remove any DC-offset from your waveform and use the offset parameter of the corresponding channel!") end @@ -150,8 +150,9 @@ function createRFFTindices(controlledChannelsDict::OrderedDict{PeriodicElectrica index_mask[i,j,dfCyclesPerPeriod+1] = true elseif isa(comp, ArbitraryElectricalComponent) # the frequency samples have a spacing dfCyclesPerPeriod in the spectrum, only use a maximum number of 2^13+1 points, since the waveform has a buffer length of 2^14 (=> rfft 2^13+1) - index_mask[i,j,dfCyclesPerPeriod+1:dfCyclesPerPeriod:minimum(rfftSize, (2^13+1)*dfCyclesPerPeriod+1)] .= true - @info "Debug: createRFFTindices" divider(comp) sum(index_mask[i,j,:]) + N_harmonics = findlast(x->x>1e-8, abs.(rfft(values(comp)/0.5length(values(comp))))) + index_mask[i,j,dfCyclesPerPeriod+1:dfCyclesPerPeriod:min(rfftSize, N_harmonics*dfCyclesPerPeriod+1)] .= true + @debug "createRFFTindices: AWG component" divider(comp) sum(index_mask[i,j,:]) N_harmonics end end index_mask[i,end,1] = ~any(index_mask[findall(x->x==refChannelIdx[i], refChannelIdx),end,1]) && channel.dcEnabled # use the first DC enabled channel going to each ref channel to control the DC value with @@ -181,6 +182,8 @@ function createRFFTindices(controlledChannelsDict::OrderedDict{PeriodicElectrica return index_mask end +allComponentMask(cont::AWControlSequence) = .|(eachslice(cont.rfftIndices, dims=2)...) + function createLUTs(seqChannel::Vector{PeriodicElectricalChannel}, seq::Sequence) N = rxNumSamplingPoints(seq) D = length(seqChannel) @@ -521,7 +524,9 @@ function checkFieldDeviation(cont::ControlSequence, txCont::TxDAQController, Γ: rel_deviation = diag(rel_deviation) phase_deviation = diag(phase_deviation) elseif isa(cont, AWControlSequence) - # TODO/JA: select only components that are needed + abs_deviation = abs_deviation[allComponentMask(cont)]' # TODO/JA: keep the distinction between the channels (maybe as Vector{Vector{}}), instead of putting everything into a long vector with unknown order + rel_deviation = rel_deviation[allComponentMask(cont)]' + phase_deviation = phase_deviation[allComponentMask(cont)]' end @debug "Check field deviation [T]" Ω Γ @debug "Ω - Γ = " abs_deviation rel_deviation phase_deviation @@ -539,7 +544,7 @@ function updateControl!(cont::ControlSequence, txCont::TxDAQController, Γ::Matr κ = calcControlMatrix(cont) newTx = updateControlMatrix(cont, txCont, Γ, Ω, κ) - if checkFieldToVolt(κ, Γ, cont, txCont) && checkVoltLimits(newTx, cont) + if checkFieldToVolt(κ, Γ, cont, txCont, Ω) && checkVoltLimits(newTx, cont) updateControlSequence!(cont, newTx) return true else @@ -570,10 +575,10 @@ function updateControlMatrix(cont::AWControlSequence, txCont::TxDAQController, # The problem is, that to achieve 0 we will always output zero, but we would need a much more sophisticated method to solve this newTx = κ./Γ.*Ω - @debug "Last TX matrix [V]:" κ - @debug "Ref matrix [T]:" Γ - @debug "Desired matrix [V]:" Ω - @debug "New TX matrix [T]:" newTx + #@debug "Last TX matrix [V]:" κ=lineplot(1:rxNumSamplingPoints(cont.currSequence),checkVoltLimits(κ,cont,return_time_signal=true)') + #@debug "Ref matrix [T]:" Γ=lineplot(1:rxNumSamplingPoints(cont.currSequence),checkVoltLimits(Γ,cont,return_time_signal=true)') + #@debug "Desired matrix [V]:" Ω=lineplot(1:rxNumSamplingPoints(cont.currSequence),checkVoltLimits(Ω,cont,return_time_signal=true)') + #@debug "New TX matrix [T]:" newTx=lineplot(1:rxNumSamplingPoints(cont.currSequence),checkVoltLimits(newTx,cont,return_time_signal=true)') return newTx end @@ -623,7 +628,7 @@ function calcFieldsFromRef(cont::AWControlSequence, uRef::Array{Float32,4}) # do rfft channel wise and correct with the transfer function, return as (num control channels x len rfft x periods x frames) Matrix, the selection of [:,:,1,1] is done in controlTx spectrum = rfft(uRef, 1)/0.5N sortedSpectrum = permutedims(spectrum[:, cont.refIndices, :, :], (2,1,3,4)) - frequencies = rfftfreq(N, rxSamplingRate(cont.currSequence)) + frequencies = ustrip.(u"Hz",rfftfreq(N, rxSamplingRate(cont.currSequence))) fb_calibration = reduce(vcat, [ustrip.(u"T/V", chan.feedback.calibration(frequencies)) for chan in getControlledDAQChannels(cont)]') return sortedSpectrum.*fb_calibration end @@ -768,7 +773,7 @@ end -function checkFieldToVolt(oldTx::Matrix{<:Complex}, Γ::Matrix{<:Complex}, cont::CrossCouplingControlSequence, txCont::TxDAQController) +function checkFieldToVolt(oldTx::Matrix{<:Complex}, Γ::Matrix{<:Complex}, cont::CrossCouplingControlSequence, txCont::TxDAQController, Ω::Matrix{<:Complex}) dividers = divider.(getPrimaryComponents(cont)) frequencies = ustrip(u"Hz", txBaseFrequency(cont.currSequence)) ./ dividers calibFieldToVoltEstimate = [ustrip(u"V/T", chan.calibration(frequencies[i])) for (i,chan) in enumerate(getControlledDAQChannels(cont))] @@ -787,18 +792,19 @@ function checkFieldToVolt(oldTx::Matrix{<:Complex}, Γ::Matrix{<:Complex}, cont: return valid end -function checkFieldToVolt(oldTx::Matrix{<:Complex}, Γ::Matrix{<:Complex}, cont::AWControlSequence, txCont::TxDAQController) +function checkFieldToVolt(oldTx::Matrix{<:Complex}, Γ::Matrix{<:Complex}, cont::AWControlSequence, txCont::TxDAQController, Ω::Matrix{<:Complex}) N = rxNumSamplingPoints(cont.currSequence) - frequencies = rfftfreq(N, rxSamplingRate(cont.currSequence)) + frequencies = ustrip.(u"Hz",rfftfreq(N, rxSamplingRate(cont.currSequence))) calibFieldToVoltEstimate = reduce(vcat,[ustrip.(u"V/T", chan.calibration(frequencies)) for chan in getControlledDAQChannels(cont)]') calibFieldToVoltMeasured = oldTx ./ Γ - abs_deviation = abs.(1.0 .- calibFieldToVoltMeasured[cont.rfftIndices,:]./calibFieldToVoltEstimate[cont.rfftIndices,:]) # TODO/JA: fix indicies!!! - phase_deviation = angle.(calibFieldToVoltMeasured[cont.rfftIndices,:]./calibFieldToVoltEstimate[cont.rfftIndices,:]) - @debug "We expected $(calibFieldToVoltEstimate) and got $(calibFieldToVoltMeasured), deviation: $abs_deviation" + mask = allComponentMask(cont) .& (abs.(Ω).>1e-15) + abs_deviation = abs.(1.0 .- calibFieldToVoltMeasured[mask]./calibFieldToVoltEstimate[mask]) + phase_deviation = angle.(calibFieldToVoltMeasured[mask]./calibFieldToVoltEstimate[mask]) + #@debug "checkFieldToVolt: We expected $(calibFieldToVoltEstimate[allComponentMask(cont)]) V/T and got $(calibFieldToVoltMeasured[allComponentMask(cont)]) V/T, deviation: $abs_deviation" valid = maximum( abs_deviation ) < txCont.params.fieldToVoltDeviation if !valid - @warn "Measured field to volt deviates by $abs_deviation from estimate, exceeding allowed deviation" + @warn "Measured field to volt deviates by $(abs_deviation*100) % from estimate, exceeding allowed deviation of $(txCont.params.fieldToVoltDeviation*100) %" elseif maximum(abs.(phase_deviation)) > 10/180*pi @warn "The phase of the measured field to volt deviates by $phase_deviation from estimate. Please check you phases! Continuing anyways..." end @@ -826,16 +832,16 @@ function checkVoltLimits(newTx::Matrix{<:Complex}, cont::AWControlSequence; retu testSignalTime = irfft(newTx, N, 2)*0.5N - validChannel = maximum(abs.(testSignalTime), dims=2) .< ustrip.(u"V", getproperty.(getControlledDAQChannels(cont),:limitPeak)) - - valid = all(validChannel) - if !valid - @debug "Valid Tx Channel" validChannel - @warn "New control sequence exceeds voltage limits of tx channel" - end if return_time_signal return testSignalTime else + validChannel = maximum(abs.(testSignalTime), dims=2) .< ustrip.(u"V", getproperty.(getControlledDAQChannels(cont),:limitPeak)) + + valid = all(validChannel) + if !valid + @debug "Valid Tx Channel" validChannel + @warn "New control sequence exceeds voltage limits of tx channel" + end return valid end end diff --git a/src/MPIMeasurements.jl b/src/MPIMeasurements.jl index 4a1e3505..4f269b41 100644 --- a/src/MPIMeasurements.jl +++ b/src/MPIMeasurements.jl @@ -18,6 +18,7 @@ using StringEncodings using DocStringExtensions using MacroTools using LibSerialPort +using UnicodePlots using ReplMaker import REPL diff --git a/src/Scanner.jl b/src/Scanner.jl index 280ed185..58413655 100644 --- a/src/Scanner.jl +++ b/src/Scanner.jl @@ -222,7 +222,7 @@ mutable struct MPIScanner throw(ScannerConfigurationError("Could not find a valid configuration for scanner with name `$name`. Search path contains the following directories: $scannerConfigurationPath.")) end - @debug "Instantiating scanner `$name` from configuration file at `$filename`." + @info "Instantiating scanner `$name` from configuration file at `$filename`." params = TOML.parsefile(filename) generalParams = params_from_dict(MPIScannerGeneral, params["General"]) From 441f7ce059d37c048cab1b5dc2facd1a61c8e7c8 Mon Sep 17 00:00:00 2001 From: nHackel Date: Fri, 1 Sep 2023 10:34:06 +0200 Subject: [PATCH 025/168] Implement init store/restore for multi seq sm --- .../MultiSequenceSystemMatrixProtocol.jl | 84 ++++++++++--------- 1 file changed, 45 insertions(+), 39 deletions(-) diff --git a/src/Protocols/MultiSequenceSystemMatrixProtocol.jl b/src/Protocols/MultiSequenceSystemMatrixProtocol.jl index f903fcfa..bfa35cbf 100644 --- a/src/Protocols/MultiSequenceSystemMatrixProtocol.jl +++ b/src/Protocols/MultiSequenceSystemMatrixProtocol.jl @@ -13,6 +13,8 @@ Base.@kwdef mutable struct MultiSequenceSystemMatrixProtocolParams <: ProtocolPa positions::Union{Positions, Nothing} = nothing "Flag if the calibration should be saved as a system matrix or not" saveAsSystemMatrix::Bool = true + "Seconds to wait between measurements" + waitTime::Float64 = 0.0 end function MultiSequenceSystemMatrixProtocolParams(dict::Dict, scanner::MPIScanner) positions = nothing @@ -132,9 +134,27 @@ function enterExecute(protocol::MultiSequenceSystemMatrixProtocol) protocol.systemMeasState.currPos = 1 end +function initMeasData(protocol::MultiSequenceSystemMatrixProtocol) + if isfile(protocol, "meta.toml") + message = """Found existing calibration file! \n + Should it be resumed?""" + if askConfirmation(protocol, message) + restore(protocol) + end + end + # Set signals to zero if we didn't restore + if !protocol.restored + signals = mmap!(protocol, "signals.bin", protocol.systemMeasState.signals); + protocol.systemMeasState.signals = signals + protocol.systemMeasState.signals[:] .= 0.0 + end +end + + function _execute(protocol::MultiSequenceSystemMatrixProtocol) @debug "Measurement protocol started" + initMeasData(protocol) finished = false notifiedStop = false @@ -287,9 +307,9 @@ function asyncConsumer(channel::Channel, protocol::MultiSequenceSystemMatrixProt calib.currentSignal = calib.signals[:, :, :, stopIdx:stopIdx] - #@info "store" - #timeStore = store(protocol, index) - #@info "done after $timeStore" + @info "store" + timeStore = @elapsed store(protocol, index) + @info "done after $timeStore" end function store(protocol::MultiSequenceSystemMatrixProtocol, index) @@ -305,16 +325,16 @@ function store(protocol::MultiSequenceSystemMatrixProtocol, index) params["measIsBGPos"] = sysObj.measIsBGPos params["posToIdx"] = sysObj.posToIdx params["measIsBGFrame"] = sysObj.measIsBGFrame - params["temperatures"] = vec(sysObj.temperatures) - params["sequence"] = toDict(protocol.params.sequence) + #params["temperatures"] = vec(sysObj.temperatures) + params["sequences"] = toDict.(protocol.params.sequences) open(filename, "w") do f TOML.print(f, params) end Mmap.sync!(sysObj.signals) - Mmap.sync!(sysObj.drivefield) - Mmap.sync!(sysObj.applied) + #Mmap.sync!(sysObj.drivefield) + #Mmap.sync!(sysObj.applied) return end @@ -330,31 +350,17 @@ function restore(protocol::MultiSequenceSystemMatrixProtocol) sysObj.measIsBGPos = params["measIsBGPos"] sysObj.posToIdx = params["posToIdx"] sysObj.measIsBGFrame = params["measIsBGFrame"] - temp = params["temperatures"] - if !isempty(temp) && (length(sysObj.temperatures) == length(temp)) - sysObj.temperatures[:] .= temp - end + #temp = params["temperatures"] + #if !isempty(temp) && (length(sysObj.temperatures) == length(temp)) + # sysObj.temperatures[:] .= temp + #end sysObj.positions = Positions(params) + seq = protocol.params.sequences - numBGPos = sum(sysObj.measIsBGPos) - numFGPos = length(sysObj.measIsBGPos) - numBGPos - - message = "Current position is $(sysObj.currPos). Resume from last background position instead?" - if askChoices(protocol, message, ["No", "Use"]) == 2 - temp = sysObj.currPos - while temp > 1 && !sysObj.measIsBGPos[temp] - temp = temp - 1 - end - sysObj.currPos = temp - end - - - seq = protocol.params.sequence - - storedSeq = sequenceFromDict(params["sequence"]) + storedSeq = sequenceFromDict.(params["sequences"]) if storedSeq != seq - message = "Stored sequence does not match initialized sequence. Use stored sequence instead?" + message = "Stored sequences do not match initialized sequence. Use stored sequence instead?" if askChoices(protocol, message, ["Cancel", "Use"]) == 1 throw(CancelException()) end @@ -363,20 +369,20 @@ function restore(protocol::MultiSequenceSystemMatrixProtocol) end # Drive Field - if isfile(protocol, "observedField.bin") # sysObj.drivefield is still empty at point of (length(sysObj.drivefield) == length(drivefield)) - sysObj.drivefield = mmap(protocol, "observedField.bin", ComplexF64) - end - if isfile(protocol, "appliedField.bin") - sysObj.applied = mmap(protocol, "appliedField.bin", ComplexF64) - end + #if isfile(protocol, "observedField.bin") # sysObj.drivefield is still empty at point of (length(sysObj.drivefield) == length(drivefield)) + # sysObj.drivefield = mmap(protocol, "observedField.bin", ComplexF64) + #end + #if isfile(protocol, "appliedField.bin") + # sysObj.applied = mmap(protocol, "appliedField.bin", ComplexF64) + #end sysObj.signals = mmap(protocol, "signals.bin", Float32) - numTotalFrames = numFGPos * protocol.params.fgFrames + protocol.params.bgFrames * numBGPos - numRxChannels = length(rxChannels(seq)) # kind of hacky, but actual rxChannels for RedPitaya are only set when setupRx is called - rxNumSamplingPoints = rxNumSamplesPerPeriod(seq) - numPeriods = acqNumPeriodsPerFrame(seq) + numTotalFrames = sum(acqNumFrames, seq) + numRxChannels = length(rxChannels(seq[1])) # kind of hacky, but actual rxChannels for RedPitaya are only set when setupRx is called + rxNumSamplingPoints = rxNumSamplesPerPeriod(seq[1]) + numPeriods = acqNumPeriodsPerFrame(seq[1]) paramSize = (rxNumSamplingPoints, numRxChannels, numPeriods, numTotalFrames) if size(sysObj.signals) != paramSize throw(DimensionMismatch("Dimensions of stored signals $(size(sysObj.signals)) does not match initialized signals $paramSize")) @@ -390,7 +396,7 @@ end function cleanup(protocol::MultiSequenceSystemMatrixProtocol) - # NOP + rm(dir(protocol), force = true, recursive = true) end function stop(protocol::MultiSequenceSystemMatrixProtocol) From 3bfa7f45899e836a9440101d0ff5cc5c9593462c Mon Sep 17 00:00:00 2001 From: nHackel Date: Fri, 1 Sep 2023 11:07:17 +0200 Subject: [PATCH 026/168] Implement wait time for multi seq sm protocol --- .../MultiSequenceSystemMatrixProtocol.jl | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/Protocols/MultiSequenceSystemMatrixProtocol.jl b/src/Protocols/MultiSequenceSystemMatrixProtocol.jl index bfa35cbf..a9df935d 100644 --- a/src/Protocols/MultiSequenceSystemMatrixProtocol.jl +++ b/src/Protocols/MultiSequenceSystemMatrixProtocol.jl @@ -202,10 +202,17 @@ function performMeasurements(protocol::MultiSequenceSystemMatrixProtocol) break end - wait(calib.producer) - wait(calib.consumer) - prepareDAQ(protocol) - # TODO wait time + wasRestored = protocol.restored + timePreparing = @elapsed begin + wait(calib.producer) + wait(calib.consumer) + prepareDAQ(protocol) + end + diffTime = protocol.params.waitTime - timePreparing + if diffTime > 0.0 && !wasRestored && protocol.systemMeasState.currPos > 1 + @info "Wait $diffTime s for next measurement" + sleep(diffTime) + end performMeasurement(protocol) if protocol.systemMeasState.currPos > length(protocol.params.sequences) @@ -236,9 +243,9 @@ function performMeasurement(protocol::MultiSequenceSystemMatrixProtocol) daq = getDAQ(protocol.scanner) sequence = protocol.params.sequences[index] - #if protocol.params.controlTx - # sequence = protocol.contSequence.targetSequence - #end + if protocol.params.controlTx + sequence = protocol.contSequence.targetSequence + end channel = Channel{channelType(daq)}(32) calib.producer = @tspawnat protocol.scanner.generalParams.producerThreadID asyncProducer(channel, daq, sequence) @@ -262,7 +269,6 @@ function prepareDAQ(protocol::MultiSequenceSystemMatrixProtocol) if protocol.params.controlTx if isnothing(protocol.contSequence) || protocol.restored || (calib.currPos == 1) protocol.contSequence = controlTx(protocol.txCont, protocol.params.sequence) - protocol.restored = false end #if isempty(protocol.systemMeasState.drivefield) # len = length(keys(protocol.contSequence.simpleChannel)) @@ -274,6 +280,9 @@ function prepareDAQ(protocol::MultiSequenceSystemMatrixProtocol) sequence = protocol.contSequence end setup(daq, sequence) + if protocol.restored + protocol.restored = false + end end function asyncConsumer(channel::Channel, protocol::MultiSequenceSystemMatrixProtocol, index) From 15616a5a4d1c688155cc66bb216fa4297a4fbbd7 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Mon, 11 Sep 2023 12:16:27 +0200 Subject: [PATCH 027/168] moved calibration for LUT channels to applyForwardCalibration --- src/Devices/DAQ/DAQ.jl | 51 ++++++++++++++------ src/Devices/DAQ/RedPitayaDAQ.jl | 21 +++----- src/Sequences/ContinuousElectricalChannel.jl | 2 +- src/Sequences/StepwiseElectricalChannel.jl | 3 +- 4 files changed, 46 insertions(+), 31 deletions(-) diff --git a/src/Devices/DAQ/DAQ.jl b/src/Devices/DAQ/DAQ.jl index a271546a..f9ad4a44 100644 --- a/src/Devices/DAQ/DAQ.jl +++ b/src/Devices/DAQ/DAQ.jl @@ -236,8 +236,6 @@ end function applyForwardCalibration!(seq::Sequence, daq::AbstractDAQ) - # TODO/JA: what about the other types of channels? - for channel in periodicElectricalTxChannels(seq) off = offset(channel) if dimension(off) != dimension(1.0u"V") @@ -246,23 +244,44 @@ function applyForwardCalibration!(seq::Sequence, daq::AbstractDAQ) end for comp in components(channel) - amp = amplitude(comp) - pha = phase(comp) - if dimension(amp) != dimension(1.0u"V") - f_comp = ustrip(u"Hz", txBaseFrequency(seq)) / divider(comp) - complex_comp = (amp*exp(im*pha)) * calibration(daq, id(channel))(f_comp) - amplitude!(comp, uconvert(u"V",abs(complex_comp))) - phase!(comp, angle(complex_comp)u"rad") - if comp isa ArbitraryElectricalComponent - N = length(values(comp)) - f_awg = rfftfreq(N, f_comp*N) - calib = calibration(daq, id(channel))(f_awg) ./ calibration(daq, id(channel))(f_comp) # since amplitude and phase are already calibrated for the base frequency, here we need to remove that factor - values!(comp, irfft(rfft(values(comp)).*calib, N)) - end + amp = amplitude(comp) + pha = phase(comp) + if dimension(amp) != dimension(1.0u"V") + f_comp = ustrip(u"Hz", txBaseFrequency(seq)) / divider(comp) + complex_comp = (amp*exp(im*pha)) * calibration(daq, id(channel))(f_comp) + amplitude!(comp, uconvert(u"V",abs(complex_comp))) + phase!(comp, angle(complex_comp)u"rad") + if comp isa ArbitraryElectricalComponent + N = length(values(comp)) + f_awg = rfftfreq(N, f_comp*N) + calib = calibration(daq, id(channel))(f_awg) ./ calibration(daq, id(channel))(f_comp) # since amplitude and phase are already calibrated for the base frequency, here we need to remove that factor + values!(comp, irfft(rfft(values(comp)).*calib, N)) end - + end end end + + for lutChannel in acyclicElectricalTxChannels(seq) + if lutChannel isa StepwiseElectricalChannel + values = values(lutChannel) + if dimension(values[1]) != dimension(1.0u"V") + values = values.*calibration(daq, id(lutChannel))(0) # use DC value for LUTChannels + values!(lutChannel, values) + end + elseif lutChannel isa ContinuousElectricalChannel + amp = lutChannel.amplitude + off = lutChannel.offset + if dimension(amp) != dimension(1.0u"V") + amp = amp*calibration(daq, id(lutChannel))(0) # use DC value for LUTChannels + lutChannel.amplitude = amp + end + if dimension(off) != dimension(1.0u"V") + off = off*calibration(daq, id(lutChannel))(0) # use DC value for LUTChannels + lutChannel.offfset = off + end + end + end + nothing end diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index aba27a05..72fe7d46 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -308,12 +308,7 @@ function createLUT!(lut::Array{Float32}, start, channelMapping) lutValues = [] lutIdx = [] for (lutChannel, seqChannel) in channelMapping - tempValues = values(seqChannel) - if !isnothing(lutChannel.calibration) - tempValues = tempValues.*lutChannel.calibration - end - tempValues = ustrip.(u"V", tempValues) - push!(lutValues, tempValues) + push!(lutValues, ustrip.(u"V", values(seqChannel))) push!(lutIdx, lutChannel.channelIdx) end @@ -501,16 +496,16 @@ end function setup(daq::RedPitayaDAQ, sequence::Sequence) stopTx(daq) setupRx(daq, sequence) - setupTx(daq, sequence) - prepareTx(daq, sequence) - setSequenceParams(daq, sequence) + sequenceVolt = applyForwardCalibration(sequence, daq) + setupTx(daq, sequenceVolt) + prepareTx(daq, sequenceVolt) + setSequenceParams(daq, sequenceVolt) end function setupTx(daq::RedPitayaDAQ, sequence::Sequence) @debug "Setup tx" - sequenceVolt = applyForwardCalibration(sequence, daq) - - periodicChannels = periodicElectricalTxChannels(sequenceVolt) + + periodicChannels = periodicElectricalTxChannels(sequence) if any([length(component.amplitude) > 1 for channel in periodicChannels for component in periodicElectricalComponents(channel)]) error("The Red Pitaya DAQ cannot work with more than one period in a frame or frequency sweeps yet.") @@ -518,7 +513,7 @@ function setupTx(daq::RedPitayaDAQ, sequence::Sequence) # Iterate over sequence(!) channels execute!(daq.rpc) do batch - baseFreq = txBaseFrequency(sequenceVolt) + baseFreq = txBaseFrequency(sequence) for channel in periodicChannels setupTxChannel!(batch, daq, channel, baseFreq) end diff --git a/src/Sequences/ContinuousElectricalChannel.jl b/src/Sequences/ContinuousElectricalChannel.jl index 712c8589..0ab203ed 100644 --- a/src/Sequences/ContinuousElectricalChannel.jl +++ b/src/Sequences/ContinuousElectricalChannel.jl @@ -1,7 +1,7 @@ export ContinuousElectricalChannel "Electrical channel with a stepwise definition of values." -Base.@kwdef struct ContinuousElectricalChannel <: AcyclicElectricalTxChannel # TODO: Why is this named continuous? +Base.@kwdef mutable struct ContinuousElectricalChannel <: AcyclicElectricalTxChannel # TODO: Why is this named continuous? "ID corresponding to the channel configured in the scanner." id::AbstractString "Divider of sampling frequency." diff --git a/src/Sequences/StepwiseElectricalChannel.jl b/src/Sequences/StepwiseElectricalChannel.jl index 556bee8e..1cb172fb 100644 --- a/src/Sequences/StepwiseElectricalChannel.jl +++ b/src/Sequences/StepwiseElectricalChannel.jl @@ -1,7 +1,7 @@ export StepwiseElectricalChannel "Electrical channel with a stepwise definition of values." -Base.@kwdef struct StepwiseElectricalChannel <: AcyclicElectricalTxChannel +Base.@kwdef mutable struct StepwiseElectricalChannel <: AcyclicElectricalTxChannel "ID corresponding to the channel configured in the scanner." id::AbstractString "Divider of the component." @@ -36,6 +36,7 @@ function createFieldChannel(channelID::AbstractString, channelType::Type{Stepwis end values(channel::StepwiseElectricalChannel) = channel.values +values!(channel::StepwiseElectricalChannel, val::Union{Vector{typeof(1.0u"T")}, Vector{typeof(1.0u"A")}, Vector{typeof(1.0u"V")}}) = channel.values = val cycleDuration(channel::StepwiseElectricalChannel, baseFrequency::typeof(1.0u"Hz")) = upreferred(channel.divider/baseFrequency) stepsPerCycle(channel::StepwiseElectricalChannel) = length(channel.values) From c6e60c7db66be994d986c1377361101175c4a833 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Mon, 11 Sep 2023 14:54:55 +0200 Subject: [PATCH 028/168] small fixes --- src/Devices/DAQ/DAQ.jl | 12 ++++++------ src/Devices/DAQ/RedPitayaDAQ.jl | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Devices/DAQ/DAQ.jl b/src/Devices/DAQ/DAQ.jl index f9ad4a44..0650ef24 100644 --- a/src/Devices/DAQ/DAQ.jl +++ b/src/Devices/DAQ/DAQ.jl @@ -263,21 +263,21 @@ function applyForwardCalibration!(seq::Sequence, daq::AbstractDAQ) for lutChannel in acyclicElectricalTxChannels(seq) if lutChannel isa StepwiseElectricalChannel - values = values(lutChannel) + values = lutChannel.values if dimension(values[1]) != dimension(1.0u"V") - values = values.*calibration(daq, id(lutChannel))(0) # use DC value for LUTChannels - values!(lutChannel, values) + values = values.*calibration(daq, id(lutChannel)) + lutChannel.values = values end elseif lutChannel isa ContinuousElectricalChannel amp = lutChannel.amplitude off = lutChannel.offset if dimension(amp) != dimension(1.0u"V") - amp = amp*calibration(daq, id(lutChannel))(0) # use DC value for LUTChannels + amp = amp*calibration(daq, id(lutChannel)) lutChannel.amplitude = amp end if dimension(off) != dimension(1.0u"V") - off = off*calibration(daq, id(lutChannel))(0) # use DC value for LUTChannels - lutChannel.offfset = off + off = off*calibration(daq, id(lutChannel)) + lutChannel.offset = off end end end diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index 72fe7d46..94dcce31 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -605,7 +605,7 @@ function setupRx(daq::RedPitayaDAQ, sequence::Sequence) # TODO possibly move some of this into abstract daq daq.refChanIDs = [] - txChannels = [channel[2] for channel in daq.params.channels if channel[2] isa TxChannelParams] + txChannels = [channel[2] for channel in daq.params.channels if channel[2] isa DAQTxChannelParams] daq.refChanIDs = unique([tx.feedback.channelID for tx in txChannels if !isnothing(tx.feedback)]) # Construct view to save bandwidth From 3324c1591a4ff220609551b347e10139a5b259b1 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Mon, 11 Sep 2023 17:47:29 +0200 Subject: [PATCH 029/168] first successful control of triangle wave --- src/Devices/DAQ/DAQ.jl | 2 +- src/Devices/Virtual/TxDAQController.jl | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Devices/DAQ/DAQ.jl b/src/Devices/DAQ/DAQ.jl index 0650ef24..b5e3dd3c 100644 --- a/src/Devices/DAQ/DAQ.jl +++ b/src/Devices/DAQ/DAQ.jl @@ -254,7 +254,7 @@ function applyForwardCalibration!(seq::Sequence, daq::AbstractDAQ) if comp isa ArbitraryElectricalComponent N = length(values(comp)) f_awg = rfftfreq(N, f_comp*N) - calib = calibration(daq, id(channel))(f_awg) ./ calibration(daq, id(channel))(f_comp) # since amplitude and phase are already calibrated for the base frequency, here we need to remove that factor + calib = calibration(daq, id(channel))(f_awg) ./ (abs.(calibration(daq, id(channel))(f_comp))*exp.(im*2*pi*range(0,length(f_awg)-1).*angle(calibration(daq, id(channel))(f_comp)))) # since amplitude and phase are already calibrated for the base frequency, here we need to remove that factor values!(comp, irfft(rfft(values(comp)).*calib, N)) end end diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index 23c3d365..336dcc5c 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -515,9 +515,9 @@ function checkFieldDeviation(cont::ControlSequence, txCont::TxDAQController, Γ: diff = Ω - Γ abs_deviation = abs.(diff) rel_deviation = abs_deviation ./ abs.(Ω) - rel_deviation[abs.(Ω).==0] .= 0 # relative deviation does not make sense for a zero goal + rel_deviation[abs.(Ω).<1e-15] .= 0 # relative deviation does not make sense for a zero goal phase_deviation = angle.(Ω).-angle.(Γ) - phase_deviation[abs.(Ω).==0] .= 0 # phase deviation does not make sense for a zero goal + phase_deviation[abs.(Ω).<1e-15] .= 0 # phase deviation does not make sense for a zero goal if !needsDecoupling(cont.targetSequence) && !isa(cont,AWControlSequence) abs_deviation = diag(abs_deviation) @@ -530,7 +530,7 @@ function checkFieldDeviation(cont::ControlSequence, txCont::TxDAQController, Γ: end @debug "Check field deviation [T]" Ω Γ @debug "Ω - Γ = " abs_deviation rel_deviation phase_deviation - @info "Observed field deviation:\n\t$(abs_deviation) T\n\t$(rel_deviation*100) %\n\t$(phase_deviation/pi*180)°\n allowed: $(txCont.params.absoluteAmplitudeAccuracy), $(txCont.params.relativeAmplitudeAccuracy*100) %, $(uconvert(u"°",txCont.params.phaseAccuracy))" + @info "Observed field deviation:\nabs:\t$(abs_deviation) T\nrel:\t$(rel_deviation*100) %\nφ:\t$(phase_deviation/pi*180)°\n allowed: $(txCont.params.absoluteAmplitudeAccuracy), $(txCont.params.relativeAmplitudeAccuracy*100) %, $(uconvert(u"°",txCont.params.phaseAccuracy))" phase_ok = abs.(phase_deviation) .< ustrip(u"rad", txCont.params.phaseAccuracy) amplitude_ok = (abs.(abs_deviation) .< ustrip(u"T", txCont.params.absoluteAmplitudeAccuracy)) .| (abs.(rel_deviation) .< txCont.params.relativeAmplitudeAccuracy) @debug "Field deviation:" amplitude_ok phase_ok @@ -740,7 +740,7 @@ end # Convert New Tx from matrix in V to currSequence function updateControlSequence!(cont::CrossCouplingControlSequence, newTx::Matrix) for (i, channel) in enumerate(periodicElectricalTxChannels(cont.currSequence)) - for (j, comp) in enumerate(periodicElectricalComponents(channel)) + for (j, comp) in enumerate(components(channel)) amplitude!(comp, abs(newTx[i, j])*1.0u"V") phase!(comp, angle(newTx[i, j])*1.0u"rad") end @@ -752,7 +752,7 @@ function updateControlSequence!(cont::AWControlSequence, newTx::Matrix) if cont.rfftIndices[i,end,1] offset!(channel, abs(newTx[i,1])*1.0u"V") end - for (j, comp) in enumerate(periodicElectricalComponents(channel)) + for (j, comp) in enumerate(components(channel)) if isa(comp, PeriodicElectricalComponent) amplitude!(comp, abs.(newTx[i, cont.rfftIndices[i,j,:]])[]*1.0u"V") phase!(comp, angle.(newTx[i, cont.rfftIndices[i,j,:]])[]*1.0u"rad"+(pi/2)u"rad") @@ -799,9 +799,9 @@ function checkFieldToVolt(oldTx::Matrix{<:Complex}, Γ::Matrix{<:Complex}, cont: calibFieldToVoltMeasured = oldTx ./ Γ mask = allComponentMask(cont) .& (abs.(Ω).>1e-15) - abs_deviation = abs.(1.0 .- calibFieldToVoltMeasured[mask]./calibFieldToVoltEstimate[mask]) - phase_deviation = angle.(calibFieldToVoltMeasured[mask]./calibFieldToVoltEstimate[mask]) - #@debug "checkFieldToVolt: We expected $(calibFieldToVoltEstimate[allComponentMask(cont)]) V/T and got $(calibFieldToVoltMeasured[allComponentMask(cont)]) V/T, deviation: $abs_deviation" + abs_deviation = abs.(1.0 .- abs.(calibFieldToVoltMeasured[mask])./abs.(calibFieldToVoltEstimate[mask])) + phase_deviation = angle.(calibFieldToVoltMeasured[mask]) .- angle.(calibFieldToVoltEstimate[mask]) + @debug "checkFieldToVolt: We expected $(calibFieldToVoltEstimate[mask]) V/T and got $(calibFieldToVoltMeasured[mask]) V/T, deviation: $abs_deviation" valid = maximum( abs_deviation ) < txCont.params.fieldToVoltDeviation if !valid @warn "Measured field to volt deviates by $(abs_deviation*100) % from estimate, exceeding allowed deviation of $(txCont.params.fieldToVoltDeviation*100) %" From 382289ba598422c91b4496abae24dec583a6b9ba Mon Sep 17 00:00:00 2001 From: nHackel Date: Fri, 15 Sep 2023 18:07:51 +0200 Subject: [PATCH 030/168] Init working on n-dims MPS protocol --- src/Devices/DAQ/RedPitayaDAQ.jl | 38 ++++++++++++++ src/MPIMeasurements.jl | 2 +- src/Protocols/MPSMeasurementProtocol.jl | 8 ++- src/Sequences/MagneticField.jl | 3 ++ .../ProtocolOffsetElectricalChannel.jl | 52 +++++++++++++++++++ src/Sequences/StepwiseElectricalChannel.jl | 2 +- src/Sequences/TxChannel.jl | 4 +- 7 files changed, 105 insertions(+), 4 deletions(-) create mode 100644 src/Sequences/ProtocolOffsetElectricalChannel.jl diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index fca3fe17..b9022939 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -444,6 +444,44 @@ function getTiming(daq::RedPitayaDAQ) return sampleTiming end +function prepareProtocolSequences(base::Sequence, daq::RedPitayaDAQ) + cpy = deepcopy(base) + + # Prepare offset sequences + offsetMap = Dict{MagneticField, Vector{ProtocolOffsetElectricalChannel}}() + for field in cpy + for channel in channels(field, ProtocolOffsetElectricalChannel) + temp = get(offsetMap, field, ProtocolOffsetElectricalChannel[]) + push!(temp, channel) + end + end + + numOffsets = 1 + for field in offsetMap + for offsetChannel in field + numOffsets*=length(values(offsetChannel)) + end + end + + divider = dfDivider(cpy) + + inner = 1 + for field in offsetMap + for offsetChannel in field + offsets = values(offsetChannel) + outer = div(numOffsets, inner * length(offsets)) + steps = repeat(offsets, inner = inner, outer = outer) + inner*=length(offsets) + stepwise = StepwiseElectricalChannel(id = id(offsetChannel), divider = divider, values = steps) + + delete!(field, id(stepwise)) + push!(field, stepwise) + end + end + + return cpy +end + #### Producer/Consumer #### mutable struct RedPitayaAsyncBuffer <: AsyncBuffer samples::Union{Matrix{Int16}, Nothing} diff --git a/src/MPIMeasurements.jl b/src/MPIMeasurements.jl index b045fb84..d75760e8 100644 --- a/src/MPIMeasurements.jl +++ b/src/MPIMeasurements.jl @@ -24,7 +24,7 @@ import REPL import REPL: LineEdit, REPLCompletions import REPL: TerminalMenus import Base.write, Base.take!, Base.put!, Base.isready, Base.isopen, Base.eltype, Base.close, Base.wait, Base.length, Base.push! -import Base: ==, isequal, hash, isfile, push!, pop!, empty!, getindex, setindex!, firstindex, lastindex, length, iterate, delete!, deleteat!, keys, haskey +import Base: ==, isequal, hash, isfile, push!, pop!, empty!, getindex, setindex!, firstindex, lastindex, length, iterate, delete!, deleteat!, keys, haskey, values # Reexporting MPIFiles is disliked by Aqua since there are undefined exports. Therefore, I disabled reexporting here. #using Reexport diff --git a/src/Protocols/MPSMeasurementProtocol.jl b/src/Protocols/MPSMeasurementProtocol.jl index bbf93fd4..a3bb14ff 100644 --- a/src/Protocols/MPSMeasurementProtocol.jl +++ b/src/Protocols/MPSMeasurementProtocol.jl @@ -62,6 +62,8 @@ Base.@kwdef mutable struct MPSMeasurementProtocol <: Protocol seqMeasState::Union{SequenceMeasState, Nothing} = nothing protocolMeasState::Union{ProtocolMeasState, Nothing} = nothing + sequences::Vector{Sequence} = Sequence[] + bgMeas::Array{Float32, 4} = zeros(Float32,0,0,0,0) done::Bool = false cancelled::Bool = false @@ -94,7 +96,11 @@ function _init(protocol::MPSMeasurementProtocol) protocol.bgMeas = zeros(Float32,0,0,0,0) protocol.protocolMeasState = ProtocolMeasState() - setupSequence(protocol) + try + protocol.sequences = prepareProtocolSequences(protocol.params.seqeuence, getDAQ(scanner(protocol))) + catch e + throw(ProtocolConfigurationError(e)) + end return nothing end diff --git a/src/Sequences/MagneticField.jl b/src/Sequences/MagneticField.jl index 7991e328..72dc396d 100644 --- a/src/Sequences/MagneticField.jl +++ b/src/Sequences/MagneticField.jl @@ -101,6 +101,9 @@ periodicElectricalTxChannels(field::MagneticField) = channels(field, PeriodicEle export acyclicElectricalTxChannels acyclicElectricalTxChannels(field::MagneticField) = channels(field, AcyclicElectricalTxChannel) +export protocolElectricalTxChannels +protocolTxChannels(field::MagneticField) = channels(field, ProtocolTxChannel) + function toDict!(dict, field::MagneticField) for structField in [x for x in fieldnames(typeof(field)) if !in(x, [:id, :channels])] dict[String(structField)] = toDictValue(getproperty(field, structField)) diff --git a/src/Sequences/ProtocolOffsetElectricalChannel.jl b/src/Sequences/ProtocolOffsetElectricalChannel.jl new file mode 100644 index 00000000..c6146624 --- /dev/null +++ b/src/Sequences/ProtocolOffsetElectricalChannel.jl @@ -0,0 +1,52 @@ +export ProtocolOffsetElectricalChannel + +Base.@kwdef struct ProtocolOffsetElectricalChannel{T<:Union{typeof(1.0u"T"),typeof(1.0u"A"),typeof(1.0u"V")}} <: ProtocolTxChannel + "ID corresponding to the channel configured in the scanner." + id::AbstractString + offsetStart::T + offsetStop::T + numOffsets::Int64 +end + +channeltype(::Type{<:ProtocolOffsetElectricalChannel}) = StepwiseTxChannel() + +function createFieldChannel(channelID::AbstractString, ::Type{<:ProtocolOffsetElectricalChannel}, channelDict::Dict{String, Any}) + splattingDict = Dict{Symbol, Any}() + splattingDict[:id] = channelID + + offsetStart = uparse.(channelDict["offsetStart"]) + if eltype(offsetStart) <: Unitful.Current + offsetStart = offsetStart .|> u"A" + elseif eltype(offsetStart) <: Unitful.Voltage + offsetStart = offsetStart .|> u"V" + elseif eltype(offsetStart) <: Unitful.BField + offsetStart = offsetStart .|> u"T" + else + error("The value for an offsetStart has to be either given as a current or in tesla. You supplied the type `$(eltype(tmp))`.") + end + + offsetStop = uparse.(channelDict["offsetStop"]) + if eltype(offsetStop) <: Unitful.Current + offsetStop = offsetStop .|> u"A" + elseif eltype(offsetStop) <: Unitful.Voltage + offsetStop = offsetStop .|> u"V" + elseif eltype(offsetStop) <: Unitful.BField + offsetStop = offsetStop .|> u"T" + else + error("The value for an offsetStop has to be either given as a current or in tesla. You supplied the type `$(eltype(tmp))`.") + end + + numOffsets = uparse(channelDict["numOffsets"]) + + return PeriodicElectricalChannel(;id = id, offsetStart = offsetStart, offsetStop = offsetStop, numOffsets = numOffsets) +end + +values(channel::ProtocolOffsetElectricalChannel{T}) where T = collect(range(channel.offsetStart, channel.offsetStop, length = channel.numOffsets)) + +function toDict!(dict, channel::ProtocolOffsetElectricalChannel) + dict["type"] = string(typeof(channel)) + for field in [x for x in fieldnames(typeof(channel)) if !in(x, [:id])] + dict[String(field)] = toDictValue(getproperty(channel, field)) + end + return dict +end \ No newline at end of file diff --git a/src/Sequences/StepwiseElectricalChannel.jl b/src/Sequences/StepwiseElectricalChannel.jl index 556bee8e..36d453f1 100644 --- a/src/Sequences/StepwiseElectricalChannel.jl +++ b/src/Sequences/StepwiseElectricalChannel.jl @@ -9,7 +9,7 @@ Base.@kwdef struct StepwiseElectricalChannel <: AcyclicElectricalTxChannel "Values corresponding to the individual steps." values::Union{Vector{typeof(1.0u"T")}, Vector{typeof(1.0u"A")}, Vector{typeof(1.0u"V")}} "TBD" - enable::Vector{Bool} + enable::Vector{Bool} = Bool[] end channeltype(::Type{<:StepwiseElectricalChannel}) = StepwiseTxChannel() diff --git a/src/Sequences/TxChannel.jl b/src/Sequences/TxChannel.jl index 41958a0b..3abc7d48 100644 --- a/src/Sequences/TxChannel.jl +++ b/src/Sequences/TxChannel.jl @@ -13,6 +13,7 @@ abstract type TxChannel end abstract type ElectricalTxChannel <: TxChannel end abstract type AcyclicElectricalTxChannel <: ElectricalTxChannel end abstract type MechanicalTxChannel <: TxChannel end +abstract type ProtocolTxChannel <: TxChannel end abstract type ElectricalComponent end @@ -65,4 +66,5 @@ include("ContinuousElectricalChannel.jl") include("ContinuousMechanicalTranslationChannel.jl") include("StepwiseMechanicalTranslationChannel.jl") include("StepwiseMechanicalRotationChannel.jl") -include("ContinuousMechanicalRotationChannel.jl") \ No newline at end of file +include("ContinuousMechanicalRotationChannel.jl") +include("ProtocolOffsetElectricalChannel.jl") \ No newline at end of file From 31b3327405a8a79cb61bd70173438ed4ea79b841 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Tue, 19 Sep 2023 17:55:14 +0200 Subject: [PATCH 031/168] fix abs to allow negative offsets --- src/Devices/DAQ/DAQ.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Devices/DAQ/DAQ.jl b/src/Devices/DAQ/DAQ.jl index b5e3dd3c..2fee0a2a 100644 --- a/src/Devices/DAQ/DAQ.jl +++ b/src/Devices/DAQ/DAQ.jl @@ -239,8 +239,8 @@ function applyForwardCalibration!(seq::Sequence, daq::AbstractDAQ) for channel in periodicElectricalTxChannels(seq) off = offset(channel) if dimension(off) != dimension(1.0u"V") - offsetVolts = off*calibration(daq, id(channel))(0) # use DC value for offsets - offset!(channel, uconvert(u"V",abs(offsetVolts))) + offsetVolts = off*abs(calibration(daq, id(channel))(0)) # use DC value for offsets + offset!(channel, uconvert(u"V",offsetVolts)) end for comp in components(channel) From 770c432240e9c69ba96c7f623bac0b21bdac5dfa Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Tue, 19 Sep 2023 17:55:31 +0200 Subject: [PATCH 032/168] added debug message --- src/Devices/DAQ/RedPitayaDAQ.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index 94dcce31..567ae25d 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -511,6 +511,8 @@ function setupTx(daq::RedPitayaDAQ, sequence::Sequence) error("The Red Pitaya DAQ cannot work with more than one period in a frame or frequency sweeps yet.") end + @debug "SetupTx: Outputting the following offsets" offsets=[offset(chan) for chan in periodicChannels] + # Iterate over sequence(!) channels execute!(daq.rpc) do batch baseFreq = txBaseFrequency(sequence) From 62238806fc98c442fbdc3c98d57b435262efd1a1 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Tue, 19 Sep 2023 17:58:58 +0200 Subject: [PATCH 033/168] small fixes --- src/Devices/Virtual/TxDAQController.jl | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index 336dcc5c..1b19b46b 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -349,13 +349,15 @@ function controlTx(txCont::TxDAQController, control::ControlSequence) # Prepare control measurement setup(daq, control.currSequence) - menu = REPL.TerminalMenus.RadioMenu(["Continue", "Abort"], pagesize=2) - choice = REPL.TerminalMenus.request("Please confirm the current values for control:", menu) - if choice == 1 - println("Continuing...") - else - println("Control cancelled") - error("Control cancelled!") + if "checkEachControlStep" in split(ENV["JULIA_DEBUG"],",") + menu = REPL.TerminalMenus.RadioMenu(["Continue", "Abort"], pagesize=2) + choice = REPL.TerminalMenus.request("Please confirm the current values for control:", menu) + if choice == 1 + println("Continuing...") + else + println("Control cancelled") + error("Control cancelled!") + end end channel = Channel{channelType(daq)}(32) @@ -443,7 +445,7 @@ function getControlResult(cont::ControlSequence)::Sequence # Use the magnetic field that are controlled from currSeq and all uncontrolled fields and general settings from target _name = "Control Result for target $(name(cont.targetSequence))" - general = GeneralSettings(;name=_name, description = description(cont.targetSequence), targetScanner = targetScanner(cont.targetSequence), baseFrequency = baseFrequency(cont.target)) + general = GeneralSettings(;name=_name, description = description(cont.targetSequence), targetScanner = targetScanner(cont.targetSequence), baseFrequency = baseFrequency(cont.targetSequence)) acq = cont.targetSequence.acquisition _fields = MagneticField[] @@ -530,7 +532,7 @@ function checkFieldDeviation(cont::ControlSequence, txCont::TxDAQController, Γ: end @debug "Check field deviation [T]" Ω Γ @debug "Ω - Γ = " abs_deviation rel_deviation phase_deviation - @info "Observed field deviation:\nabs:\t$(abs_deviation) T\nrel:\t$(rel_deviation*100) %\nφ:\t$(phase_deviation/pi*180)°\n allowed: $(txCont.params.absoluteAmplitudeAccuracy), $(txCont.params.relativeAmplitudeAccuracy*100) %, $(uconvert(u"°",txCont.params.phaseAccuracy))" + @info "Observed field deviation:\nabs:\t$(abs_deviation*1000) mT\nrel:\t$(rel_deviation*100) %\nφ:\t$(phase_deviation/pi*180)°\n allowed: $(txCont.params.absoluteAmplitudeAccuracy|>u"mT"), $(txCont.params.relativeAmplitudeAccuracy*100) %, $(uconvert(u"°",txCont.params.phaseAccuracy))" phase_ok = abs.(phase_deviation) .< ustrip(u"rad", txCont.params.phaseAccuracy) amplitude_ok = (abs.(abs_deviation) .< ustrip(u"T", txCont.params.absoluteAmplitudeAccuracy)) .| (abs.(rel_deviation) .< txCont.params.relativeAmplitudeAccuracy) @debug "Field deviation:" amplitude_ok phase_ok @@ -627,6 +629,7 @@ function calcFieldsFromRef(cont::AWControlSequence, uRef::Array{Float32,4}) N = rxNumSamplingPoints(cont.currSequence) # do rfft channel wise and correct with the transfer function, return as (num control channels x len rfft x periods x frames) Matrix, the selection of [:,:,1,1] is done in controlTx spectrum = rfft(uRef, 1)/0.5N + spectrum[1,:,:,:] ./= 2 sortedSpectrum = permutedims(spectrum[:, cont.refIndices, :, :], (2,1,3,4)) frequencies = ustrip.(u"Hz",rfftfreq(N, rxSamplingRate(cont.currSequence))) fb_calibration = reduce(vcat, [ustrip.(u"T/V", chan.feedback.calibration(frequencies)) for chan in getControlledDAQChannels(cont)]') From 970ab9c067596d944266b6b63cfde4095069bdc9 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Tue, 19 Sep 2023 18:00:01 +0200 Subject: [PATCH 034/168] implement zeroing of DC component --- src/Devices/Virtual/TxDAQController.jl | 79 ++++++++++++++++++++------ 1 file changed, 63 insertions(+), 16 deletions(-) diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index 1b19b46b..6f750ed9 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -7,7 +7,7 @@ Base.@kwdef mutable struct TxDAQControllerParams <: DeviceParams absoluteAmplitudeAccuracy::typeof(1.0u"T") = 50.0u"µT" maxControlSteps::Int64 = 20 fieldToVoltDeviation::Float64 = 0.2 - controlDC::Bool = false + findZeroDC::Bool = false end TxDAQControllerParams(dict::Dict) = params_from_dict(TxDAQControllerParams, dict) @@ -33,7 +33,7 @@ mutable struct AWControlSequence <: ControlSequence controlledChannelsDict::OrderedDict{PeriodicElectricalChannel, TxChannelParams} refIndices::Vector{Int64} rfftIndices::BitArray{3} # Matrix of size length(controlledChannelsDict) x 4 (max. num of components) x len(rfft) - # Arbitrary Waveform + dcCorrection::Union{Vector{Float64}, Nothing} end @enum ControlResult UNCHANGED UPDATED INVALID @@ -73,7 +73,7 @@ function ControlSequence(txCont::TxDAQController, target::Sequence, daq::Abstrac controlledChannelsDict = createControlledChannelsDict(seqControlledChannels, daq) # should this be changed to components instead of channels? refIndices = createReferenceIndexMapping(controlledChannelsDict, daq) - controlSequenceType = decideControlSequenceType(target) + controlSequenceType = decideControlSequenceType(target, txCont.params.findZeroDC) @debug "ControlSequence: Decided on using ControlSequence of type $controlSequenceType" if controlSequenceType==CrossCouplingControlSequence @@ -105,24 +105,24 @@ function ControlSequence(txCont::TxDAQController, target::Sequence, daq::Abstrac rfftIndices = createRFFTindices(controlledChannelsDict, target, daq) - return AWControlSequence(target, currSeq, controlledChannelsDict, refIndices, rfftIndices) + return AWControlSequence(target, currSeq, controlledChannelsDict, refIndices, rfftIndices, nothing) end end -function decideControlSequenceType(target::Sequence) +function decideControlSequenceType(target::Sequence, findZeroDC::Bool=false) hasAWComponent = any(isa.([component for channel in getControlledChannels(target) for component in channel.components], ArbitraryElectricalComponent)) moreThanOneComponent = any(x -> length(x.components) > 1, getControlledChannels(target)) moreThanThreeChannels = length(getControlledChannels(target)) > 3 moreThanOneField = length(getControlledFields(target)) > 1 needsDecoupling_ = needsDecoupling(target) - @debug "decideControlSequenceType:" hasAWComponent moreThanOneComponent moreThanThreeChannels moreThanOneField needsDecoupling_ + @debug "decideControlSequenceType:" hasAWComponent moreThanOneComponent moreThanThreeChannels moreThanOneField needsDecoupling_ findZeroDC - if needsDecoupling_ && !hasAWComponent && !moreThanOneField && !moreThanThreeChannels && !moreThanOneComponent + if needsDecoupling_ && !hasAWComponent && !moreThanOneField && !moreThanThreeChannels && !moreThanOneComponent && findZeroDC return CrossCouplingControlSequence elseif needsDecoupling_ - throw(SequenceConfigurationError("The given sequence can not be controlled! To control a field with decoupling it cannot have an AW component ($hasAWComponent), more than one field ($moreThanOneField), more than three channels ($moreThanThreeChannels) nor more than one component per channel ($moreThanOneComponent)")) - elseif !hasAWComponent && !moreThanOneField && !moreThanThreeChannels && !moreThanOneComponent + throw(SequenceConfigurationError("The given sequence can not be controlled! To control a field with decoupling it cannot have an AW component ($hasAWComponent), more than one field ($moreThanOneField), more than three channels ($moreThanThreeChannels) nor more than one component per channel ($moreThanOneComponent). DC control ($findZeroDC) is also not possible")) + elseif !hasAWComponent && !moreThanOneField && !moreThanThreeChannels && !moreThanOneComponent && findZeroDC return CrossCouplingControlSequence else return AWControlSequence @@ -292,6 +292,29 @@ function controlTx(txCont::TxDAQController, seq::Sequence, ::Nothing = nothing) end end +function measureMeanReferenceSignal(daq::AbstractDAQ, seq::Sequence) + channel = Channel{channelType(daq)}(32) + buf = AsyncBuffer(SimpleFrameBuffer(1, zeros(Float32,rxNumSamplesPerPeriod(seq),length(daq.rpv)*2,acqNumPeriodsPerFrame(seq),acqNumFrames(seq))), daq) + @info "DC Control measurement started" + producer = @async begin + @debug "Starting DC control producer" + endSample = startProducer(channel, daq, 1) + endSequence(daq, endSample) + end + bind(channel, producer) + consumer = @async begin + while isopen(channel) || isready(channel) + while isready(channel) + chunk = take!(channel) + push!(buf, chunk) + end + sleep(0.001) + end + end + + wait(consumer) + return mean(read(buf.target)[:,channelIdx(daq, daq.refChanIDs),1,1], dims=1) +end function controlTx(txCont::TxDAQController, control::ControlSequence) # Prepare and check channel under control @@ -335,13 +358,33 @@ function controlTx(txCont::TxDAQController, control::ControlSequence) i = 1 try - if txCont.params.controlDC # is this only possible with AWControlSequence?? -> maybe do for all channels that are dcEnabled - # create sequence with every field off, dc set to zero, basically trigger off + if txCont.params.findZeroDC # this is only possible with AWControlSequence + # create sequence with every field off, dc set to zero # calculate mean for each ref channel - # create sequence with offsets set to -mean(ref)(V)*feedback.calibration(T/V)*forwardcalibration(V/T) - # optional: send sequence and calculate perfect zero with two points - # update redpitaya DAC calibration values - error("The DC offset control is not yet implemented") + # create sequence with offsets set to small value + # measure men for each ref channel again + # use the two measurements to find the DC value that needs to be output to achieve zero on the reference channel + # always add found value to DC values in control + dc_cont = deepcopy(control) + signals = zeros(ComplexF64,controlMatrixShape(control)) + updateControlSequence!(dc_cont, signals) + setup(daq, dc_cont.currSequence) + ref_offsets_0 = measureMeanReferenceSignal(daq, dc_cont.currSequence)[control.refIndices] + + δ = ustrip.(u"V", [1u"mT"*abs(calibration(daq, id(channel))(0)) for channel in getControlledChannels(control)]) # use DC value for offsets + @debug "findZeroDC: Now outputting the following DC values: [V]" δ + signals[:,1] .= δ + updateControlSequence!(dc_cont, signals) + setup(daq, dc_cont.currSequence) + ref_offsets_δ = measureMeanReferenceSignal(daq, dc_cont.currSequence)[control.refIndices] + + dc_correction = -ref_offsets_0./((ref_offsets_δ.-ref_offsets_0)./δ) + + if any(abs.(dc_correction).>maximum(δ)) # We do not accept any change that is larger than what we would expect for 1mT + error("DC correction too large!") + end + @debug "Result of finding Zero DC" ref_offsets_0' ref_offsets_δ' dc_correction' + control.dcCorrection = dc_correction end while !controlPhaseDone && i <= txCont.params.maxControlSteps @@ -753,7 +796,11 @@ end function updateControlSequence!(cont::AWControlSequence, newTx::Matrix) for (i, channel) in enumerate(periodicElectricalTxChannels(cont.currSequence)) if cont.rfftIndices[i,end,1] - offset!(channel, abs(newTx[i,1])*1.0u"V") + if !isnothing(cont.dcCorrection) + offset!(channel, (cont.dcCorrection[i]+real(newTx[i,1]))*1.0u"V") + else + offset!(channel, real(newTx[i,1])*1.0u"V") + end end for (j, comp) in enumerate(components(channel)) if isa(comp, PeriodicElectricalComponent) From 5e93bc7ae70c28562b8828842400d4228fc0846b Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Tue, 19 Sep 2023 18:00:20 +0200 Subject: [PATCH 035/168] test time based convergence for AW --- src/Devices/Virtual/TxDAQController.jl | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index 6f750ed9..472ff255 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -572,6 +572,12 @@ function checkFieldDeviation(cont::ControlSequence, txCont::TxDAQController, Γ: abs_deviation = abs_deviation[allComponentMask(cont)]' # TODO/JA: keep the distinction between the channels (maybe as Vector{Vector{}}), instead of putting everything into a long vector with unknown order rel_deviation = rel_deviation[allComponentMask(cont)]' phase_deviation = phase_deviation[allComponentMask(cont)]' + Γt = checkVoltLimits(Γ,cont,return_time_signal=true)' + Ωt = checkVoltLimits(Ω,cont,return_time_signal=true)' + + diff = (Ωt .- Γt) + @debug "checkFieldDeviation" diff=lineplot(1:rxNumSamplingPoints(cont.currSequence),diff) + @debug "checkFieldDeviation" max_diff = maximum(abs.(diff)) end @debug "Check field deviation [T]" Ω Γ @debug "Ω - Γ = " abs_deviation rel_deviation phase_deviation @@ -582,6 +588,18 @@ function checkFieldDeviation(cont::ControlSequence, txCont::TxDAQController, Γ: return all(phase_ok) && all(amplitude_ok) end +function checkFieldDeviation(cont::AWControlSequence, txCont::TxDAQController, Γ::Matrix{<:Complex}, Ω::Matrix{<:Complex}) + + Γt = checkVoltLimits(Γ,cont,return_time_signal=true)' + Ωt = checkVoltLimits(Ω,cont,return_time_signal=true)' + + diff = (Ωt .- Γt) + @debug "checkFieldDeviation" diff=lineplot(1:rxNumSamplingPoints(cont.currSequence),diff) + @debug "checkFieldDeviation" max_diff = maximum(abs.(diff)) + amplitude_ok = abs.(diff).< ustrip(u"T", txCont.params.absoluteAmplitudeAccuracy) + return all(amplitude_ok) +end + updateControl!(cont::ControlSequence, txCont::TxDAQController, uRef) = updateControl!(cont, txCont, calcFieldFromRef(cont, uRef), calcDesiredField(cont)) function updateControl!(cont::ControlSequence, txCont::TxDAQController, Γ::Matrix{<:Complex}, Ω::Matrix{<:Complex}) From c6d4dba89773baf3bcc3ceb1e882b0259aaf584c Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Tue, 19 Sep 2023 18:23:05 +0200 Subject: [PATCH 036/168] indexing fixed --- src/Devices/Virtual/TxDAQController.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index 472ff255..6be83782 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -754,7 +754,7 @@ function calcDesiredField(cont::AWControlSequence) for (i, channel) in enumerate(getControlledChannels(cont.targetSequence)) if cont.rfftIndices[i,end,1] - desiredField[1,i] = ustrip(u"T", offset(channel)) + desiredField[i,1] = ustrip(u"T", offset(channel)) end for (j, comp) in enumerate(components(channel)) From 0fbd95d301706b1f81bfa26c4c50e06f4dd9d661 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Wed, 20 Sep 2023 15:47:37 +0200 Subject: [PATCH 037/168] increase allowed jump in waveform --- src/Sequences/PeriodicElectricalChannel.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Sequences/PeriodicElectricalChannel.jl b/src/Sequences/PeriodicElectricalChannel.jl index ab78de02..b472fbb8 100644 --- a/src/Sequences/PeriodicElectricalChannel.jl +++ b/src/Sequences/PeriodicElectricalChannel.jl @@ -132,7 +132,7 @@ function createChannelComponent(componentID::AbstractString, ::Type{ArbitraryEle values = componentDict["values"] end - if abs(values[1]-values[end])>0.001 # is the jump limit of 0.1% too strict? + if abs(values[1]-values[end])>0.01 # is the jump limit of 0.1% too strict?, yes, probably 1% is fine throw(SequenceConfigurationError("The first and last value of a waveform should be close enough together to not produce a jump! Please check your waveform")) end From 807af8f87eda8be2b3789905239d9fe93dde6a92 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Wed, 20 Sep 2023 15:49:06 +0200 Subject: [PATCH 038/168] added slew rate check --- src/Devices/DAQ/DAQ.jl | 5 +++++ src/Devices/Virtual/TxDAQController.jl | 13 +++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Devices/DAQ/DAQ.jl b/src/Devices/DAQ/DAQ.jl index 2fee0a2a..9e37496d 100644 --- a/src/Devices/DAQ/DAQ.jl +++ b/src/Devices/DAQ/DAQ.jl @@ -58,6 +58,7 @@ end Base.@kwdef mutable struct DAQTxChannelParams <: TxChannelParams channelIdx::Int64 limitPeak::typeof(1.0u"V") + limitSlewRate::typeof(1.0u"V/s") = 1000.0u"V/µs" # default is basically no limit sinkImpedance::SinkImpedance = SINK_HIGH allowedWaveforms::Vector{Waveform} = [WAVEFORM_SINE] feedback::Union{DAQFeedback, Nothing} = nothing @@ -73,6 +74,10 @@ function createDAQChannel(::Type{DAQTxChannelParams}, dict::Dict{String,Any}) splattingDict[:channelIdx] = dict["channel"] splattingDict[:limitPeak] = uparse(dict["limitPeak"]) + if haskey(dict, "limitSlewRate") + splattingDict[:limitSlewRate] = uparse(dict["limitSlewRate"]) + end + if haskey(dict, "sinkImpedance") splattingDict[:sinkImpedance] = dict["sinkImpedance"] == "FIFTY_OHM" ? SINK_FIFTY_OHM : SINK_HIGH end diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index 6be83782..d1702a58 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -898,17 +898,22 @@ function checkVoltLimits(newTx::Matrix{<:Complex}, cont::AWControlSequence; retu validChannel = zeros(Bool, numControlledChannels(cont)) N = rxNumSamplingPoints(cont.currSequence) - testSignalTime = irfft(newTx, N, 2)*0.5N + spectrum = copy(newTx)*0.5N + spectrum[:,1] .*= 2 + testSignalTime = irfft(spectrum, N, 2) if return_time_signal return testSignalTime else - validChannel = maximum(abs.(testSignalTime), dims=2) .< ustrip.(u"V", getproperty.(getControlledDAQChannels(cont),:limitPeak)) + slew_rate = (diff(testSignalTime, dims=2)*u"V"*rxSamplingRate(cont.currSequence)) .|> u"V/µs" + validSlew = maximum(abs.(slew_rate), dims=2) .< getproperty.(getControlledDAQChannels(cont),:limitSlewRate) + validPeak = maximum(abs.(testSignalTime), dims=2) .< ustrip.(u"V", getproperty.(getControlledDAQChannels(cont),:limitPeak)) - valid = all(validChannel) + valid = all(validSlew) && all(validPeak) + @debug "Check Volt Limit" p=lineplot(1:N,testSignalTime') maximum(abs.(testSignalTime), dims=2) maximum(abs.(slew_rate), dims=2) if !valid @debug "Valid Tx Channel" validChannel - @warn "New control sequence exceeds voltage limits of tx channel" + @warn "New control sequence exceeds voltage limits (slew rate or peak) of tx channel" end return valid end From ed70b019e2a4d6c34b9bbb7aa6f35847044a7ca7 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Thu, 21 Sep 2023 07:51:34 +0200 Subject: [PATCH 039/168] add volt limit check to initial guess --- src/Devices/Virtual/TxDAQController.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index d1702a58..2db8916c 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -322,6 +322,9 @@ function controlTx(txCont::TxDAQController, control::ControlSequence) Ω = calcDesiredField(control) txCont.cont = control + if !checkVoltLimits(calcControlMatrix(control), control) + error("Initial guess for the controller already exceeds the possibilities of the DAQ!") + end # Start Tx su = nothing @@ -895,7 +898,6 @@ function checkVoltLimits(newTx::Matrix{<:Complex}, cont::CrossCouplingControlSeq end function checkVoltLimits(newTx::Matrix{<:Complex}, cont::AWControlSequence; return_time_signal=false) - validChannel = zeros(Bool, numControlledChannels(cont)) N = rxNumSamplingPoints(cont.currSequence) spectrum = copy(newTx)*0.5N @@ -912,7 +914,7 @@ function checkVoltLimits(newTx::Matrix{<:Complex}, cont::AWControlSequence; retu valid = all(validSlew) && all(validPeak) @debug "Check Volt Limit" p=lineplot(1:N,testSignalTime') maximum(abs.(testSignalTime), dims=2) maximum(abs.(slew_rate), dims=2) if !valid - @debug "Valid Tx Channel" validChannel + @debug "Valid Tx Channel" validSlew validPeak @warn "New control sequence exceeds voltage limits (slew rate or peak) of tx channel" end return valid From 5669b243bfbc32ad436fb4366b00b50572d2996e Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Thu, 21 Sep 2023 07:56:39 +0200 Subject: [PATCH 040/168] changed jump detection error to warning --- src/Sequences/PeriodicElectricalChannel.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Sequences/PeriodicElectricalChannel.jl b/src/Sequences/PeriodicElectricalChannel.jl index b472fbb8..cbc6f50d 100644 --- a/src/Sequences/PeriodicElectricalChannel.jl +++ b/src/Sequences/PeriodicElectricalChannel.jl @@ -132,8 +132,8 @@ function createChannelComponent(componentID::AbstractString, ::Type{ArbitraryEle values = componentDict["values"] end - if abs(values[1]-values[end])>0.01 # is the jump limit of 0.1% too strict?, yes, probably 1% is fine - throw(SequenceConfigurationError("The first and last value of a waveform should be close enough together to not produce a jump! Please check your waveform")) + if abs(values[1]-values[end])>0.01 # more than 1% of max value in 1 of 2^14 samples -> slew > 160 + @warn "The first and last value of your selected waveform are producing a jump of size $(abs(values[1]-values[end]))! Please check your waveform, if this is intended!" end return ArbitraryElectricalComponent(id=componentID, divider=divider,amplitude=amplitude, phase=phase, values=values) From cb94c930cde4d08960fe10303704b400b9a4bbbb Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Thu, 21 Sep 2023 08:00:01 +0200 Subject: [PATCH 041/168] relax threshhold for mean of waveform --- src/Devices/Virtual/TxDAQController.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index 2db8916c..f1a9e77a 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -99,7 +99,7 @@ function ControlSequence(txCont::TxDAQController, target::Sequence, daq::Abstrac elseif controlSequenceType == AWControlSequence # use the new controller # AW offset will get ignored -> should be zero - if any(x->any(mean.(scaledValues.(arbitraryElectricalComponents(x))).>1u"µT"), getControlledChannels(target)) + if any(x->any(mean.(scaledValues.(arbitraryElectricalComponents(x))).>10u"µT"), getControlledChannels(target)) error("The DC-component of arbitrary waveform components cannot be handled during control! Please remove any DC-offset from your waveform and use the offset parameter of the corresponding channel!") end From 2d708b14ba07c506ad86b6031e8742e94bc1d327 Mon Sep 17 00:00:00 2001 From: nHackel Date: Fri, 22 Sep 2023 13:15:01 +0200 Subject: [PATCH 042/168] Add setindex! with string index --- src/Sequences/MagneticField.jl | 7 +++++++ src/Sequences/PeriodicElectricalChannel.jl | 7 +++++++ src/Sequences/Sequence.jl | 9 ++++++++- 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/Sequences/MagneticField.jl b/src/Sequences/MagneticField.jl index 72dc396d..dbcabc5c 100644 --- a/src/Sequences/MagneticField.jl +++ b/src/Sequences/MagneticField.jl @@ -50,6 +50,13 @@ function getindex(field::MagneticField, index::String) throw(KeyError(index)) end setindex!(field::MagneticField, txChannel::TxChannel, i::Integer) = channels(field)[i] = txChannel +function setindex!(field::MagneticField, txChannel::TxChannel, i::String) + for (index, channel) in enumerate(channels(field)) + if isequal(id(channel), i) + return setindex!(field, txChannel, index) + end + end +end firstindex(field::MagneticField) = start_(field) lastindex(field::MagneticField) = length(field) keys(field::MagneticField) = map(id, field) diff --git a/src/Sequences/PeriodicElectricalChannel.jl b/src/Sequences/PeriodicElectricalChannel.jl index 6ae57dc2..379a8e15 100644 --- a/src/Sequences/PeriodicElectricalChannel.jl +++ b/src/Sequences/PeriodicElectricalChannel.jl @@ -61,6 +61,13 @@ function getindex(ch::PeriodicElectricalChannel, index::String) throw(KeyError(index)) end setindex!(ch::PeriodicElectricalChannel, comp::ElectricalComponent, i::Integer) = components(ch)[i] = comp +function setindex!(ch::PeriodicElectricalChannel, comp::ElectricalComponent, i::String) + for (index, cmp) in enumerate(components(ch)) + if isequal(id(cmp), i) + return setindex!(field, comp, index) + end + end +end firstindex(ch::PeriodicElectricalChannel) = start_(ch) lastindex(ch::PeriodicElectricalChannel) = length(ch) keys(ch::PeriodicElectricalChannel) = map(id, ch) diff --git a/src/Sequences/Sequence.jl b/src/Sequences/Sequence.jl index 2711d4dc..5b385079 100644 --- a/src/Sequences/Sequence.jl +++ b/src/Sequences/Sequence.jl @@ -53,6 +53,13 @@ function getindex(seq::Sequence, index::String) throw(KeyError(index)) end setindex!(seq::Sequence, field::MagneticField, i::Integer) = fields(seq)[i] = field +function setindex!(seq::Sequence, field::MagneticField, i::String) + for (index, f) in enumerate(fields(seq)) + if id(f) == i + return setindex!(seq, field, index) + end + end +end firstindex(seq::Sequence) = start_(seq) lastindex(seq::Sequence) = length(seq) keys(seq::Sequence) = map(id, seq) @@ -471,7 +478,7 @@ rxChannels(sequence::Sequence) = rxChannels(sequence.acquisition) for T in [Sequence, GeneralSettings, AcquisitionSettings, MagneticField, TxChannel, ContinuousElectricalChannel, ContinuousMechanicalRotationChannel, ContinuousMechanicalTranslationChannel, PeriodicElectricalChannel, PeriodicElectricalComponent, SweepElectricalComponent, StepwiseElectricalChannel, - StepwiseMechanicalRotationChannel, StepwiseMechanicalTranslationChannel, ArbitraryElectricalComponent] + StepwiseMechanicalRotationChannel, StepwiseMechanicalTranslationChannel, ArbitraryElectricalComponent, ProtocolOffsetElectricalChannel] @eval begin @generated function ==(x::$T, y::$T) fieldEqualities = [:(x.$field == y.$field) for field in fieldnames($T)] From 2428bf94f737cc97107d7b8ffb8a4e1592947516 Mon Sep 17 00:00:00 2001 From: nHackel Date: Fri, 22 Sep 2023 14:20:31 +0200 Subject: [PATCH 043/168] Init working generating signal offset sequence --- src/Devices/DAQ/RedPitayaDAQ.jl | 15 +++++++-------- src/Sequences/ProtocolOffsetElectricalChannel.jl | 7 ++----- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index b9022939..5817748e 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -453,29 +453,28 @@ function prepareProtocolSequences(base::Sequence, daq::RedPitayaDAQ) for channel in channels(field, ProtocolOffsetElectricalChannel) temp = get(offsetMap, field, ProtocolOffsetElectricalChannel[]) push!(temp, channel) + offsetMap[field] = temp end end numOffsets = 1 - for field in offsetMap - for offsetChannel in field + for (field, channels) in offsetMap + for offsetChannel in channels numOffsets*=length(values(offsetChannel)) end end - divider = dfDivider(cpy) + divider = lcm(dfDivider(cpy)) * numOffsets inner = 1 - for field in offsetMap - for offsetChannel in field + for (field, channels) in offsetMap + for offsetChannel in channels offsets = values(offsetChannel) outer = div(numOffsets, inner * length(offsets)) steps = repeat(offsets, inner = inner, outer = outer) inner*=length(offsets) stepwise = StepwiseElectricalChannel(id = id(offsetChannel), divider = divider, values = steps) - - delete!(field, id(stepwise)) - push!(field, stepwise) + field[id(stepwise)] = stepwise end end diff --git a/src/Sequences/ProtocolOffsetElectricalChannel.jl b/src/Sequences/ProtocolOffsetElectricalChannel.jl index c6146624..a31c092d 100644 --- a/src/Sequences/ProtocolOffsetElectricalChannel.jl +++ b/src/Sequences/ProtocolOffsetElectricalChannel.jl @@ -11,9 +11,6 @@ end channeltype(::Type{<:ProtocolOffsetElectricalChannel}) = StepwiseTxChannel() function createFieldChannel(channelID::AbstractString, ::Type{<:ProtocolOffsetElectricalChannel}, channelDict::Dict{String, Any}) - splattingDict = Dict{Symbol, Any}() - splattingDict[:id] = channelID - offsetStart = uparse.(channelDict["offsetStart"]) if eltype(offsetStart) <: Unitful.Current offsetStart = offsetStart .|> u"A" @@ -36,9 +33,9 @@ function createFieldChannel(channelID::AbstractString, ::Type{<:ProtocolOffsetEl error("The value for an offsetStop has to be either given as a current or in tesla. You supplied the type `$(eltype(tmp))`.") end - numOffsets = uparse(channelDict["numOffsets"]) + numOffsets = channelDict["numOffsets"] - return PeriodicElectricalChannel(;id = id, offsetStart = offsetStart, offsetStop = offsetStop, numOffsets = numOffsets) + return ProtocolOffsetElectricalChannel(;id = channelID, offsetStart = offsetStart, offsetStop = offsetStop, numOffsets = numOffsets) end values(channel::ProtocolOffsetElectricalChannel{T}) where T = collect(range(channel.offsetStart, channel.offsetStop, length = channel.numOffsets)) From 4c9d16d1c7b9cb6c91efa7b9db5362e4f60cccbc Mon Sep 17 00:00:00 2001 From: nHackel Date: Fri, 22 Sep 2023 15:48:39 +0200 Subject: [PATCH 044/168] Attach hbridge to txSlow --- src/Devices/DAQ/DAQ.jl | 24 ++++++++++++++++++++++++ src/Devices/DAQ/RedPitayaDAQ.jl | 8 +++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/Devices/DAQ/DAQ.jl b/src/Devices/DAQ/DAQ.jl index 46859ebb..b0b99440 100644 --- a/src/Devices/DAQ/DAQ.jl +++ b/src/Devices/DAQ/DAQ.jl @@ -55,6 +55,30 @@ Base.@kwdef struct DAQFeedback calibration::Union{typeof(1.0u"T/V"), Nothing} = nothing end +Base.@kwdef struct DAQHBridge + channelID::AbstractString + manual::Bool = false + deadTime::typeof(1.0u"s") = 0.0u"s" + level::Vector{typeof(1.0u"V")} # Can this be simplified by a convention for h-bridges? +end +negativeLevel(bridge::DAQHBridge) = bridge.level[1] +positiveLevel(bridge::DAQHBridge) = bridge.level[2] + +function createDAQChannels(::Type{DAQHBridge}, dict::Dict{String, Any}) + splattingDict = Dict{Symbol, Any}() + splattingDict[:channelID] = dict["channelID"] + splattingDict[:level] = uparse.(dict["level"]) + + if haskey(dict, "manual") + splattingDict[:manual] = dict["manual"] + end + + if haskey(dict, "deadTime") + splattingDict[:deadTime] = uparse(dict["deadTime"]) + end + return DAQHBridge(;splattingDict...) +end + Base.@kwdef struct DAQTxChannelParams <: TxChannelParams channelIdx::Int64 limitPeak::typeof(1.0u"V") diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index 5817748e..4de3ee0b 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -33,6 +33,7 @@ end Base.@kwdef struct RedPitayaLUTChannelParams <: TxChannelParams channelIdx::Int64 calibration::Union{typeof(1.0u"V/T"), typeof(1.0u"V/A"), Nothing} = nothing + hbridge::Union{DAQHBridge, Nothing} = nothing end "Create the params struct from a dict. Typically called during scanner instantiation." @@ -84,7 +85,12 @@ function createDAQChannels(::Type{RedPitayaDAQParams}, dict::Dict{String, Any}) error("The values have to be either given as a V/t or in V/A. You supplied the type `$(eltype(calib))`.") end end - channels[key] = RedPitayaLUTChannelParams(channelIdx=value["channel"], calibration = calib) + + hbridge = nothing + if haskey(value, "hbridge") + hbridge = createDAQChannels(DAQHBridge, value["hbridge"]) + end + channels[key] = RedPitayaLUTChannelParams(channelIdx=value["channel"], calibration = calib, hbridge = hbridge) end end From ffeb1eb8ddfa61253de296c94bb35b14b9968037 Mon Sep 17 00:00:00 2001 From: nHackel Date: Fri, 22 Sep 2023 17:07:01 +0200 Subject: [PATCH 045/168] Add value range --- src/Devices/DAQ/DAQ.jl | 10 ++++++++ src/Devices/DAQ/RedPitayaDAQ.jl | 43 +++++++++++++++++---------------- src/Devices/Devices.jl | 2 +- 3 files changed, 33 insertions(+), 22 deletions(-) diff --git a/src/Devices/DAQ/DAQ.jl b/src/Devices/DAQ/DAQ.jl index b0b99440..675cc3a6 100644 --- a/src/Devices/DAQ/DAQ.jl +++ b/src/Devices/DAQ/DAQ.jl @@ -15,6 +15,13 @@ abstract type DAQParams <: DeviceParams end SINK_HIGH end +@enum TxValueRange begin + #POSITIVE + #NEGATIVE + BOTH + HBRIDGE +end + struct DAQTxChannelSettings "Applied channel voltage. Dimensions are (components, channels, periods)." amplitudes::Array{typeof(1.0u"V"), 3} @@ -63,6 +70,9 @@ Base.@kwdef struct DAQHBridge end negativeLevel(bridge::DAQHBridge) = bridge.level[1] positiveLevel(bridge::DAQHBridge) = bridge.level[2] +manual(bridge::DAQHBridge) = bridge.manual +deadTime(bridge::DAQHBridge) = bridge.deadTime +id(bridge::DAQHBridge) = bridge.channelID function createDAQChannels(::Type{DAQHBridge}, dict::Dict{String, Any}) splattingDict = Dict{Symbol, Any}() diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index 4de3ee0b..c03dbca6 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -33,6 +33,7 @@ end Base.@kwdef struct RedPitayaLUTChannelParams <: TxChannelParams channelIdx::Int64 calibration::Union{typeof(1.0u"V/T"), typeof(1.0u"V/A"), Nothing} = nothing + range::TxValueRange = BOTH hbridge::Union{DAQHBridge, Nothing} = nothing end @@ -73,7 +74,7 @@ function createDAQChannels(::Type{RedPitayaDAQParams}, dict::Dict{String, Any}) elseif value["type"] == "rx" channels[key] = DAQRxChannelParams(channelIdx=value["channel"]) elseif value["type"] == "txSlow" - calib = nothing + splattingDict[:channelIdx] = value["channel"] if haskey(value, "calibration") calib = uparse.(value["calibration"]) @@ -84,13 +85,17 @@ function createDAQChannels(::Type{RedPitayaDAQParams}, dict::Dict{String, Any}) else error("The values have to be either given as a V/t or in V/A. You supplied the type `$(eltype(calib))`.") end + splattingDict[:calibration] = calib end - hbridge = nothing if haskey(value, "hbridge") - hbridge = createDAQChannels(DAQHBridge, value["hbridge"]) + splattingDict[:hbridge] = createDAQChannels(DAQHBridge, value["hbridge"]) + end + + if haskey(value, "range") + splattingDict[:range] = value["range"] end - channels[key] = RedPitayaLUTChannelParams(channelIdx=value["channel"], calibration = calib, hbridge = hbridge) + channels[key] = RedPitayaLUTChannelParams(;splattingDict...) end end @@ -454,34 +459,30 @@ function prepareProtocolSequences(base::Sequence, daq::RedPitayaDAQ) cpy = deepcopy(base) # Prepare offset sequences - offsetMap = Dict{MagneticField, Vector{ProtocolOffsetElectricalChannel}}() + offsetVector = ProtocolOffsetElectricalChannel[] + fieldMap = Dict{ProtocolOffsetElectricalChannel, MagneticField}() for field in cpy for channel in channels(field, ProtocolOffsetElectricalChannel) - temp = get(offsetMap, field, ProtocolOffsetElectricalChannel[]) - push!(temp, channel) - offsetMap[field] = temp + push!(offsetVector, channel) + fieldMap[field] = channel end end numOffsets = 1 - for (field, channels) in offsetMap - for offsetChannel in channels - numOffsets*=length(values(offsetChannel)) - end + for offsetChannel in offsetVector + numOffsets*=length(values(offsetChannel)) end divider = lcm(dfDivider(cpy)) * numOffsets inner = 1 - for (field, channels) in offsetMap - for offsetChannel in channels - offsets = values(offsetChannel) - outer = div(numOffsets, inner * length(offsets)) - steps = repeat(offsets, inner = inner, outer = outer) - inner*=length(offsets) - stepwise = StepwiseElectricalChannel(id = id(offsetChannel), divider = divider, values = steps) - field[id(stepwise)] = stepwise - end + for offsetChannel in offsetVector + offsets = values(offsetChannel) + outer = div(numOffsets, inner * length(offsets)) + steps = repeat(offsets, inner = inner, outer = outer) + inner*=length(offsets) + stepwise = StepwiseElectricalChannel(id = id(offsetChannel), divider = divider, values = steps) + fieldMap[offsetChannel][id(stepwise)] = stepwise end return cpy diff --git a/src/Devices/Devices.jl b/src/Devices/Devices.jl index 262d5ccd..0892c7bd 100644 --- a/src/Devices/Devices.jl +++ b/src/Devices/Devices.jl @@ -15,7 +15,7 @@ include("Virtual/Virtual.jl") # List our own enums to avoid accidentally converting a different enum # Did not list enums like LakeShoreF71GaussMeterConnectionModes atm, because their convert function uses specific strings # and not the enum name -for enum in [RedPitayaDAQServer.TriggerMode, RampingMode, TemperatureControlMode] +for enum in [RedPitayaDAQServer.TriggerMode, RampingMode, TemperatureControlMode, TxValueRange] @eval begin function Base.convert(::Type{$enum}, x::String) try From 24886fc63c4530dc5ea6ae5979d4f8fc9be0d890 Mon Sep 17 00:00:00 2001 From: nHackel Date: Fri, 29 Sep 2023 11:48:31 +0200 Subject: [PATCH 046/168] Init offset sequence considering h-bridges --- src/Devices/DAQ/RedPitayaDAQ.jl | 58 ++++++++++++++++++++++++++++----- 1 file changed, 50 insertions(+), 8 deletions(-) diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index c03dbca6..972be0d6 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -455,6 +455,7 @@ function getTiming(daq::RedPitayaDAQ) return sampleTiming end + function prepareProtocolSequences(base::Sequence, daq::RedPitayaDAQ) cpy = deepcopy(base) @@ -464,7 +465,7 @@ function prepareProtocolSequences(base::Sequence, daq::RedPitayaDAQ) for field in cpy for channel in channels(field, ProtocolOffsetElectricalChannel) push!(offsetVector, channel) - fieldMap[field] = channel + fieldMap[channel] = field end end @@ -475,15 +476,56 @@ function prepareProtocolSequences(base::Sequence, daq::RedPitayaDAQ) divider = lcm(dfDivider(cpy)) * numOffsets - inner = 1 - for offsetChannel in offsetVector - offsets = values(offsetChannel) - outer = div(numOffsets, inner * length(offsets)) - steps = repeat(offsets, inner = inner, outer = outer) - inner*=length(offsets) - stepwise = StepwiseElectricalChannel(id = id(offsetChannel), divider = divider, values = steps) + offsets = [values(channel) for channel in offsetVector] + for (i,offsetChannel) in enumerate(offsetVector) + steps = Union{eltype(offsets[i]), Missing}[] + @info typeof(steps) + + # Consider H-Bridges before current channel + if i == 1 + steps = offsets[i] + else + for offset in offsets[i] + temp = eltype(steps)[offset] + for (j, otherChannel) in enumerate(offsetVector[1:i-1]) + hbridge = findfirst(ismissing, offsets[j]) + if isnothing(hbridge) + temp = repeat(temp, length(offsets[j])) + else + temp = vcat(repeat(temp, length(1:hbridge)), [missing], length(hbridge+1:length(offsets[j]))) + end + end + push!(steps, temp...) + end + end + + # Consider H-Bridge after current channel + for (j,otherChannel) in enumerate(offsetVector[i+1:end]) + hbridge = findfirst(ismissing, offsets[j]) + if isnothing(hbridge) + temp = repeat(steps, length(offsets[j])) + @info typeof(temp) + else + temp = repeat(offset, length(1:hbridge)) + push!(temp, missing) + cat(temp, repeat(offset, length(hbridge+1:length(offsets[j])))) + end + steps = temp + end + + stepwise = StepwiseElectricalChannel(id = id(offsetChannel), divider = divider, values = identity.(steps)) fieldMap[offsetChannel][id(stepwise)] = stepwise end + + #inner = 1 + #for offsetChannel in offsetVector + # offsets = values(offsetChannel) + # outer = div(numOffsets, inner * length(offsets)) + # steps = repeat(offsets, inner = inner, outer = outer) + # inner*=length(offsets) + # stepwise = StepwiseElectricalChannel(id = id(offsetChannel), divider = divider, values = steps) + # fieldMap[offsetChannel][id(stepwise)] = stepwise + #end return cpy end From 2a4fb81318ed6c17eec9d665a695d5f752a00225 Mon Sep 17 00:00:00 2001 From: nHackel Date: Fri, 29 Sep 2023 15:49:22 +0200 Subject: [PATCH 047/168] Init working protocol-offset -> stepwise processing with h-bridge pauses Missing H-bridge lut channels + their values --- src/Devices/DAQ/RedPitayaDAQ.jl | 119 ++++++++++++++------- src/Sequences/StepwiseElectricalChannel.jl | 2 +- 2 files changed, 83 insertions(+), 38 deletions(-) diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index 972be0d6..de814fb4 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -468,31 +468,30 @@ function prepareProtocolSequences(base::Sequence, daq::RedPitayaDAQ) fieldMap[channel] = field end end - - numOffsets = 1 - for offsetChannel in offsetVector - numOffsets*=length(values(offsetChannel)) - end - - divider = lcm(dfDivider(cpy)) * numOffsets - offsets = [values(channel) for channel in offsetVector] + allOffsets = [values(channel, daq) for channel in offsetVector] + allSteps = Vector{Vector{Any}}() for (i,offsetChannel) in enumerate(offsetVector) - steps = Union{eltype(offsets[i]), Missing}[] - @info typeof(steps) + steps = Union{eltype(allOffsets[i]), Missing}[] # Consider H-Bridges before current channel if i == 1 - steps = offsets[i] + steps = allOffsets[i] else - for offset in offsets[i] + # Step size is the same for each RP in a cluster + # Therefore the 2:n offsets have to hold each of their values for a whole repetition of all previous channels + # If a previous channel inserted a missing due to an h-bridge, the current channel has to do too + for offset in allOffsets[i] temp = eltype(steps)[offset] - for (j, otherChannel) in enumerate(offsetVector[1:i-1]) - hbridge = findfirst(ismissing, offsets[j]) - if isnothing(hbridge) - temp = repeat(temp, length(offsets[j])) - else - temp = vcat(repeat(temp, length(1:hbridge)), [missing], length(hbridge+1:length(offsets[j]))) + if !ismissing(offset) + for (j, otherChannel) in enumerate(offsetVector[1:i-1]) + hbridge = findfirst(ismissing, allOffsets[j]) + numOffsets = length(filter(!ismissing, allOffsets[j])) # Do not consider h-bridge switches for repeating offsets + if isnothing(hbridge) + temp = repeat(temp, numOffsets) + else + temp = vcat(repeat(temp, length(1:hbridge-1)), [missing], repeat(temp, length(hbridge+1:length(allOffsets[j])))) + end end end push!(steps, temp...) @@ -501,35 +500,81 @@ function prepareProtocolSequences(base::Sequence, daq::RedPitayaDAQ) # Consider H-Bridge after current channel for (j,otherChannel) in enumerate(offsetVector[i+1:end]) - hbridge = findfirst(ismissing, offsets[j]) + hbridge = findfirst(ismissing, allOffsets[j+i]) + numOffsets = length(filter(!ismissing, allOffsets[j+i])) # Do not consider h-bridge switches for repeating offsets if isnothing(hbridge) - temp = repeat(steps, length(offsets[j])) - @info typeof(temp) + temp = repeat(steps, numOffsets) else - temp = repeat(offset, length(1:hbridge)) - push!(temp, missing) - cat(temp, repeat(offset, length(hbridge+1:length(offsets[j])))) + temp = vcat(repeat(steps, length(1:hbridge-1)), [missing], repeat(steps, length(hbridge+1:length(allOffsets[j+i])))) end steps = temp end - - stepwise = StepwiseElectricalChannel(id = id(offsetChannel), divider = divider, values = identity.(steps)) - fieldMap[offsetChannel][id(stepwise)] = stepwise + + push!(allSteps, steps) + end + + # Prepare divider without h-bridge pauses + numOffsets = length(filter(!ismissing, allSteps[1])) + df = lcm(dfDivider(cpy)) + divider = df * numOffsets + + # H-Bridges are present + if numOffsets != length(allSteps[1]) + #time = maxSwitchTime(offsetVector, daq) + #numSwitchPeriods = ceil(Int64, time/lcm(dfDivider(cpy))) + + numSwitchPeriods = 100 + divider = df * (numSwitchPeriods + numOffsets) + # Add numSwitchPeriods 0 for every missing value + for (i, steps) in enumerate(allSteps) + temp = [] + foreach(x-> ismissing(x) ? push!(temp, fill(zero(eltype(steps[1])), numSwitchPeriods)...) : push!(temp, x), steps) + allSteps[i] = temp + end + + numOffsets = length(allSteps[1]) + df = lcm(dfDivider(cpy)) + divider = df * numOffsets + end + + for (i,offsetChannel) in enumerate(offsetVector) + stepwise = StepwiseElectricalChannel(id = id(offsetChannel), divider = divider, values = identity.(allSteps[i]), enable = fill(true, length(allSteps[i]))) + fieldMap[offsetChannel][id(stepwise)] = stepwise end - - #inner = 1 - #for offsetChannel in offsetVector - # offsets = values(offsetChannel) - # outer = div(numOffsets, inner * length(offsets)) - # steps = repeat(offsets, inner = inner, outer = outer) - # inner*=length(offsets) - # stepwise = StepwiseElectricalChannel(id = id(offsetChannel), divider = divider, values = steps) - # fieldMap[offsetChannel][id(stepwise)] = stepwise - #end return cpy end +function values(offsetChannel::ProtocolOffsetElectricalChannel{T}, daq::RedPitayaDAQ) where T + offsets = values(offsetChannel) + ch = channel(daq, id(offsetChannel)) + if ch.range == BOTH + return offsets + elseif ch.range == HBRIDGE + # Assumption protocol offset channel can only produce one sign change + # Consider 0 to be "positive" + ispositive = map(x->x >= zero(T), offsets) + sign = first(ispositive) + idx = nothing + for (i, offset) in enumerate(ispositive) + if sign != offset + idx = i + break + end + sign = offset + end + + # Mark sign change + if !isnothing(idx) + offsets = vcat(offsets[1:idx], [missing], offsets[idx+1:end]) + end + return map(abs, offsets) + else + error("Unexpected channel range") + end +end + + #### Producer/Consumer #### mutable struct RedPitayaAsyncBuffer <: AsyncBuffer samples::Union{Matrix{Int16}, Nothing} diff --git a/src/Sequences/StepwiseElectricalChannel.jl b/src/Sequences/StepwiseElectricalChannel.jl index 36d453f1..6d2aeae5 100644 --- a/src/Sequences/StepwiseElectricalChannel.jl +++ b/src/Sequences/StepwiseElectricalChannel.jl @@ -16,7 +16,7 @@ channeltype(::Type{<:StepwiseElectricalChannel}) = StepwiseTxChannel() function createFieldChannel(channelID::AbstractString, channelType::Type{StepwiseElectricalChannel}, channelDict::Dict{String, Any}) divider = channelDict["divider"] - enable = haskey(channelDict, "enable") ? parsePossibleURange(channelDict["enable"]) : Bool[] + enable = haskey(channelDict, "enable") ? channelDict["enable"] : Bool[] values = parsePossibleURange(channelDict["values"]) if eltype(values) <: Unitful.Current values = values .|> u"A" From 2ab1f5f2513eccb5949360ee4a4f4b98a68446b8 Mon Sep 17 00:00:00 2001 From: nHackel Date: Thu, 5 Oct 2023 11:05:14 +0200 Subject: [PATCH 048/168] Minimize h-bridge switches by permuting order of offsets --- src/Devices/DAQ/RedPitayaDAQ.jl | 122 ++++++++---------- .../ProtocolOffsetElectricalChannel.jl | 1 + 2 files changed, 58 insertions(+), 65 deletions(-) diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index de814fb4..d451382e 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -469,111 +469,103 @@ function prepareProtocolSequences(base::Sequence, daq::RedPitayaDAQ) end end - allOffsets = [values(channel, daq) for channel in offsetVector] - allSteps = Vector{Vector{Any}}() - for (i,offsetChannel) in enumerate(offsetVector) - steps = Union{eltype(allOffsets[i]), Missing}[] - - # Consider H-Bridges before current channel - if i == 1 - steps = allOffsets[i] - else - # Step size is the same for each RP in a cluster - # Therefore the 2:n offsets have to hold each of their values for a whole repetition of all previous channels - # If a previous channel inserted a missing due to an h-bridge, the current channel has to do too - for offset in allOffsets[i] - temp = eltype(steps)[offset] - if !ismissing(offset) - for (j, otherChannel) in enumerate(offsetVector[1:i-1]) - hbridge = findfirst(ismissing, allOffsets[j]) - numOffsets = length(filter(!ismissing, allOffsets[j])) # Do not consider h-bridge switches for repeating offsets - if isnothing(hbridge) - temp = repeat(temp, numOffsets) - else - temp = vcat(repeat(temp, length(1:hbridge-1)), [missing], repeat(temp, length(hbridge+1:length(allOffsets[j])))) - end - end - end - push!(steps, temp...) + + switchingIndices = findall(x->requireHBridge(x,daq), offsetVector) + switchingChannel = offsetVector[switchingIndices] + regularChannel = isempty(switchingChannel) ? offsetVector : offsetVector[filter(!in(switchingIndices), 1:length(offsetVector))] + allSteps = Dict{ProtocolOffsetElectricalChannel, Vector{Any}}() + + if !isempty(switchingChannel) + for comb in 0:2^length(switchingChannel)-1 + quadrant = map(Bool, digits(comb, base = 2, pad=length(switchingChannel))) + + offsets = Vector{Vector{Any}}() + + for ch in regularChannel + push!(offsets, values(ch)) end - end + for (i, isPositive) in enumerate(quadrant) + push!(offsets, values(switchingChannel[i], isPositive)) + end + quadrantSteps = prepareOffsets(offsets, daq) - # Consider H-Bridge after current channel - for (j,otherChannel) in enumerate(offsetVector[i+1:end]) - hbridge = findfirst(ismissing, allOffsets[j+i]) - numOffsets = length(filter(!ismissing, allOffsets[j+i])) # Do not consider h-bridge switches for repeating offsets - if isnothing(hbridge) - temp = repeat(steps, numOffsets) - else - temp = vcat(repeat(steps, length(1:hbridge-1)), [missing], repeat(steps, length(hbridge+1:length(allOffsets[j+i])))) + for (i, channel) in enumerate(Iterators.flatten((regularChannel, switchingChannel))) + steps = get(allSteps, channel, []) + push!(steps, quadrantSteps[i]...) + + # If not at the end then add missing + if comb != 2^length(switchingChannel)-1 + push!(steps, missing) + end + + allSteps[channel] = steps end - steps = temp end - - push!(allSteps, steps) end + # Prepare divider without h-bridge pauses - numOffsets = length(filter(!ismissing, allSteps[1])) + numSteps = length(first(allSteps)[2]) + numOffsets = length(filter(!ismissing, first(allSteps)[2])) df = lcm(dfDivider(cpy)) divider = df * numOffsets # H-Bridges are present - if numOffsets != length(allSteps[1]) + if numOffsets != length(numSteps) #time = maxSwitchTime(offsetVector, daq) #numSwitchPeriods = ceil(Int64, time/lcm(dfDivider(cpy))) - numSwitchPeriods = 100 + numSwitchPeriods = 10 divider = df * (numSwitchPeriods + numOffsets) # Add numSwitchPeriods 0 for every missing value - for (i, steps) in enumerate(allSteps) + for (channel, steps) in allSteps temp = [] foreach(x-> ismissing(x) ? push!(temp, fill(zero(eltype(steps[1])), numSwitchPeriods)...) : push!(temp, x), steps) - allSteps[i] = temp + allSteps[channel] = temp end - numOffsets = length(allSteps[1]) + numOffsets = length(first(allSteps)[2]) df = lcm(dfDivider(cpy)) divider = df * numOffsets end - for (i,offsetChannel) in enumerate(offsetVector) - stepwise = StepwiseElectricalChannel(id = id(offsetChannel), divider = divider, values = identity.(allSteps[i]), enable = fill(true, length(allSteps[i]))) - fieldMap[offsetChannel][id(stepwise)] = stepwise + for (channel, steps) in allSteps + stepwise = StepwiseElectricalChannel(id = id(channel), divider = divider, values = identity.(steps), enable = fill(true, length(steps))) + fieldMap[channel][id(stepwise)] = stepwise end return cpy end -function values(offsetChannel::ProtocolOffsetElectricalChannel{T}, daq::RedPitayaDAQ) where T +function requireHBridge(offsetChannel::ProtocolOffsetElectricalChannel{T}, daq::RedPitayaDAQ) where T offsets = values(offsetChannel) ch = channel(daq, id(offsetChannel)) if ch.range == BOTH - return offsets + return false elseif ch.range == HBRIDGE # Assumption protocol offset channel can only produce one sign change # Consider 0 to be "positive" - ispositive = map(x->x >= zero(T), offsets) - sign = first(ispositive) - idx = nothing - for (i, offset) in enumerate(ispositive) - if sign != offset - idx = i - break - end - sign = offset - end - - # Mark sign change - if !isnothing(idx) - offsets = vcat(offsets[1:idx], [missing], offsets[idx+1:end]) - end - return map(abs, offsets) + first = offsets[1] + last = offsets[end] + return signbit(first) != signbit(last) && (!iszero(first) && !iszero(last)) else error("Unexpected channel range") end end +function prepareOffsets(offsetVectors::Vector{Vector{T}}, daq::RedPitayaDAQ) where T + result = Vector{Vector{Any}}() + inner = 1 + numOffsets = prod(length.(offsetVectors)) + for offsets in offsetVectors + outer = div(numOffsets, inner * length(offsets)) + steps = repeat(offsets, inner = inner, outer = outer) + inner*=length(offsets) + push!(result, steps) + end + return map(x -> identity.(x), result) # Improve typing from Vector{Vector{Any}} +end + #### Producer/Consumer #### mutable struct RedPitayaAsyncBuffer <: AsyncBuffer diff --git a/src/Sequences/ProtocolOffsetElectricalChannel.jl b/src/Sequences/ProtocolOffsetElectricalChannel.jl index a31c092d..ece1f00f 100644 --- a/src/Sequences/ProtocolOffsetElectricalChannel.jl +++ b/src/Sequences/ProtocolOffsetElectricalChannel.jl @@ -39,6 +39,7 @@ function createFieldChannel(channelID::AbstractString, ::Type{<:ProtocolOffsetEl end values(channel::ProtocolOffsetElectricalChannel{T}) where T = collect(range(channel.offsetStart, channel.offsetStop, length = channel.numOffsets)) +values(channel::ProtocolOffsetElectricalChannel, isPositive::Bool) = filter(x-> signbit(x) != isPositive, values(channel)) function toDict!(dict, channel::ProtocolOffsetElectricalChannel) dict["type"] = string(typeof(channel)) From 9da5c1ba5e031ce2743514a7e740f6d101826d9b Mon Sep 17 00:00:00 2001 From: nHackel Date: Thu, 5 Oct 2023 13:43:31 +0200 Subject: [PATCH 049/168] Automatically add hbridge channel for offset channel --- src/Devices/DAQ/RedPitayaDAQ.jl | 48 +++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index d451382e..fb886e27 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -475,6 +475,7 @@ function prepareProtocolSequences(base::Sequence, daq::RedPitayaDAQ) regularChannel = isempty(switchingChannel) ? offsetVector : offsetVector[filter(!in(switchingIndices), 1:length(offsetVector))] allSteps = Dict{ProtocolOffsetElectricalChannel, Vector{Any}}() + # Prepare basic offsets without h-bridge switches if !isempty(switchingChannel) for comb in 0:2^length(switchingChannel)-1 quadrant = map(Bool, digits(comb, base = 2, pad=length(switchingChannel))) @@ -504,24 +505,48 @@ function prepareProtocolSequences(base::Sequence, daq::RedPitayaDAQ) end - # Prepare divider without h-bridge pauses + # Add H-Bridge channel ignoring switches + hbridges = Dict{ProtocolOffsetElectricalChannel, Vector{Any}}() + for (ch, steps) in allSteps + offsetChannel = channel(daq, id(ch)) + if offsetChannel.range == HBRIDGE + hsteps = map(x-> ismissing(x) ? missing : level(offsetChannel.hbridge, x), steps) + hbridges[ch] = hsteps + end + end + + # Prepare divider without h-bridge switch pauses numSteps = length(first(allSteps)[2]) numOffsets = length(filter(!ismissing, first(allSteps)[2])) df = lcm(dfDivider(cpy)) divider = df * numOffsets - # H-Bridges are present + # H-Bridge switches are present if numOffsets != length(numSteps) #time = maxSwitchTime(offsetVector, daq) #numSwitchPeriods = ceil(Int64, time/lcm(dfDivider(cpy))) - numSwitchPeriods = 10 + numSwitchPeriods = 100 divider = df * (numSwitchPeriods + numOffsets) # Add numSwitchPeriods 0 for every missing value - for (channel, steps) in allSteps + for (ch, steps) in allSteps temp = [] foreach(x-> ismissing(x) ? push!(temp, fill(zero(eltype(steps[1])), numSwitchPeriods)...) : push!(temp, x), steps) - allSteps[channel] = temp + allSteps[ch] = temp + + # Add next hbridge level for each missing value + if haskey(hbridges, ch) + temp = [] + for (i, x) in enumerate(hbridges[ch]) # Assumption no H-Bridge in last value + if ismissing(x) + # For the switch take the level for the value that comes after missing + push!(temp, fill(level(channel(daq, id(ch)).hbridge, hbridges[ch][i + 1]), numSwitchPeriods)...) + else + push!(temp, x) + end + end + hbridges[ch] = temp + end end numOffsets = length(first(allSteps)[2]) @@ -529,9 +554,16 @@ function prepareProtocolSequences(base::Sequence, daq::RedPitayaDAQ) divider = df * numOffsets end - for (channel, steps) in allSteps - stepwise = StepwiseElectricalChannel(id = id(channel), divider = divider, values = identity.(steps), enable = fill(true, length(steps))) - fieldMap[channel][id(stepwise)] = stepwise + # Generate actual StepwiseElectricalChannel + for (ch, steps) in allSteps + stepwise = StepwiseElectricalChannel(id = id(ch), divider = divider, values = identity.(steps), enable = fill(true, length(steps))) + fieldMap[ch][id(stepwise)] = stepwise + if haskey(hbridges, ch) + offsetChannel = channel(daq, id(ch)) + hbridgeChannel = offsetChannel.hbridge + hbridgeStepwise = StepwiseElectricalChannel(id = id(hbridgeChannel), divider = divider, values = identity.(hbridges[ch]), enable = fill(true, length(steps))) + fieldMap[ch][id(hbridgeChannel)] = hbridgeStepwise + end end return cpy From badba571f7256eb4b923fa5d8e276e81c97dd4c8 Mon Sep 17 00:00:00 2001 From: nHackel Date: Thu, 5 Oct 2023 13:44:11 +0200 Subject: [PATCH 050/168] Fix setindex! for seq, fields, electr. channel if key string was missing, default to push! --- src/Sequences/MagneticField.jl | 1 + src/Sequences/PeriodicElectricalChannel.jl | 1 + src/Sequences/Sequence.jl | 16 +++++++++------- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Sequences/MagneticField.jl b/src/Sequences/MagneticField.jl index dbcabc5c..9dcc986e 100644 --- a/src/Sequences/MagneticField.jl +++ b/src/Sequences/MagneticField.jl @@ -56,6 +56,7 @@ function setindex!(field::MagneticField, txChannel::TxChannel, i::String) return setindex!(field, txChannel, index) end end + push!(field, txChannel) end firstindex(field::MagneticField) = start_(field) lastindex(field::MagneticField) = length(field) diff --git a/src/Sequences/PeriodicElectricalChannel.jl b/src/Sequences/PeriodicElectricalChannel.jl index 379a8e15..021ca11c 100644 --- a/src/Sequences/PeriodicElectricalChannel.jl +++ b/src/Sequences/PeriodicElectricalChannel.jl @@ -67,6 +67,7 @@ function setindex!(ch::PeriodicElectricalChannel, comp::ElectricalComponent, i:: return setindex!(field, comp, index) end end + push!(ch, comp) end firstindex(ch::PeriodicElectricalChannel) = start_(ch) lastindex(ch::PeriodicElectricalChannel) = length(ch) diff --git a/src/Sequences/Sequence.jl b/src/Sequences/Sequence.jl index 5b385079..93018df9 100644 --- a/src/Sequences/Sequence.jl +++ b/src/Sequences/Sequence.jl @@ -59,6 +59,7 @@ function setindex!(seq::Sequence, field::MagneticField, i::String) return setindex!(seq, field, index) end end + push!(seq, field) end firstindex(seq::Sequence) = start_(seq) lastindex(seq::Sequence) = length(seq) @@ -330,13 +331,14 @@ export acqOffsetField function acqOffsetField(sequence::Sequence) # TODO: This is a hack for getting the required information for the MPSMeasurementProtocol. Can we find a generalized solution? if hasAcyclicElectricalTxChannels(sequence) - @warn "This is a hack for the MPSMeasurementProtocol. It might result in wrong MDF settings in other cases." - channels = acyclicElectricalTxChannels(sequence) - offsetChannel = first([channel for channel in channels if channel isa ContinuousElectricalChannel]) - values_ = MPIMeasurements.values(offsetChannel) - values3D = reshape([values_ fill(0.0u"T", length(values_)) fill(0.0u"T", length(values_))], (length(values_), 1, 3)) - - return values3D + #@warn "This is a hack for the MPSMeasurementProtocol. It might result in wrong MDF settings in other cases." + #channels = acyclicElectricalTxChannels(sequence) + #offsetChannel = first([channel for channel in channels if channel isa ContinuousElectricalChannel]) + #values_ = MPIMeasurements.values(offsetChannel) + #values3D = reshape([values_ fill(0.0u"T", length(values_)) fill(0.0u"T", length(values_))], (length(values_), 1, 3)) + # + #return values3D + return nothing else return nothing end From 27e99df3d02bc4178a9569079a056b0c1cb7257c Mon Sep 17 00:00:00 2001 From: nHackel Date: Thu, 5 Oct 2023 13:44:27 +0200 Subject: [PATCH 051/168] Add utiltiy function resolve hbridge level for given number --- src/Devices/DAQ/DAQ.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Devices/DAQ/DAQ.jl b/src/Devices/DAQ/DAQ.jl index 675cc3a6..3f1b12f0 100644 --- a/src/Devices/DAQ/DAQ.jl +++ b/src/Devices/DAQ/DAQ.jl @@ -70,6 +70,7 @@ Base.@kwdef struct DAQHBridge end negativeLevel(bridge::DAQHBridge) = bridge.level[1] positiveLevel(bridge::DAQHBridge) = bridge.level[2] +level(bridge::DAQHBridge, x::Number) = signbit(x) ? negativeLevel(bridge) : positiveLevel(bridge) manual(bridge::DAQHBridge) = bridge.manual deadTime(bridge::DAQHBridge) = bridge.deadTime id(bridge::DAQHBridge) = bridge.channelID From 91866563bea5e1ab789c0c35bc9f623063f6b3e6 Mon Sep 17 00:00:00 2001 From: nHackel Date: Thu, 5 Oct 2023 14:17:17 +0200 Subject: [PATCH 052/168] Add offsets for purely non-switching channel --- src/Devices/DAQ/RedPitayaDAQ.jl | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index fb886e27..b4a0a171 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -502,6 +502,15 @@ function prepareProtocolSequences(base::Sequence, daq::RedPitayaDAQ) allSteps[channel] = steps end end + else + offsets = Vector{Vector{Any}}() + for ch in regularChannel + push!(offsets, values(ch)) + end + offsets = prepareOffsets(offsets, daq) + for (i, channel) in enumerate(regularChannel) + allSteps[channel] = offsets[i] + end end From 92dea72aed8ebc1d93e871f2e5bcfffb659de181 Mon Sep 17 00:00:00 2001 From: nHackel Date: Thu, 5 Oct 2023 18:00:32 +0200 Subject: [PATCH 053/168] Refactor and add enable computation --- src/Devices/DAQ/RedPitayaDAQ.jl | 174 ++++++++++++++++++-------------- 1 file changed, 97 insertions(+), 77 deletions(-) diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index b4a0a171..60ed36da 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -456,6 +456,7 @@ function getTiming(daq::RedPitayaDAQ) end +# TODO add deadPeriods function prepareProtocolSequences(base::Sequence, daq::RedPitayaDAQ) cpy = deepcopy(base) @@ -469,113 +470,132 @@ function prepareProtocolSequences(base::Sequence, daq::RedPitayaDAQ) end end + allSteps = nothing + enables = nothing + hbridges = nothing + if any.(x -> requireHBridge(x, daq), offsetVector) + allSteps, enables, hbridges = prepareHSwitchedOffsets(offsetVector, daq, cpy) + else + allSteps, enables, hbridges = prepareOffsets(offsetVector, daq, cpy) + end + + numOffsets = length(first(allSteps)[2]) + df = lcm(dfDivider(cpy)) + divider = df * numOffsets + # Generate actual StepwiseElectricalChannel + for (ch, steps) in allSteps + stepwise = StepwiseElectricalChannel(id = id(ch), divider = divider, values = identity.(steps), enable = enables[ch]) + fieldMap[ch][id(stepwise)] = stepwise + if haskey(hbridges, ch) + offsetChannel = channel(daq, id(ch)) + hbridgeChannel = offsetChannel.hbridge + hbridgeStepwise = StepwiseElectricalChannel(id = id(hbridgeChannel), divider = divider, values = identity.(hbridges[ch]), enable = fill(true, length(steps))) + fieldMap[ch][id(hbridgeChannel)] = hbridgeStepwise + end + end + + return cpy +end + +function prepareHSwitchedOffsets(offsetVector::Vector{ProtocolOffsetElectricalChannel}, daq::RedPitayaDAQ, seq::Sequence) switchingIndices = findall(x->requireHBridge(x,daq), offsetVector) switchingChannel = offsetVector[switchingIndices] regularChannel = isempty(switchingChannel) ? offsetVector : offsetVector[filter(!in(switchingIndices), 1:length(offsetVector))] allSteps = Dict{ProtocolOffsetElectricalChannel, Vector{Any}}() + enables = Dict{ProtocolOffsetElectricalChannel, Vector{Bool}}() - # Prepare basic offsets without h-bridge switches - if !isempty(switchingChannel) - for comb in 0:2^length(switchingChannel)-1 - quadrant = map(Bool, digits(comb, base = 2, pad=length(switchingChannel))) - - offsets = Vector{Vector{Any}}() - - for ch in regularChannel - push!(offsets, values(ch)) - end - for (i, isPositive) in enumerate(quadrant) - push!(offsets, values(switchingChannel[i], isPositive)) - end - quadrantSteps = prepareOffsets(offsets, daq) - - for (i, channel) in enumerate(Iterators.flatten((regularChannel, switchingChannel))) - steps = get(allSteps, channel, []) - push!(steps, quadrantSteps[i]...) - - # If not at the end then add missing - if comb != 2^length(switchingChannel)-1 - push!(steps, missing) - end + # Prepare values with missing for h-bridge switch + # Permute patches by considering quadrants. Use lower bits of number to encode permutations + for comb in 0:2^length(switchingChannel)-1 + quadrant = map(Bool, digits(comb, base = 2, pad=length(switchingChannel))) - allSteps[channel] = steps - end - end - else offsets = Vector{Vector{Any}}() + + # Same order here as in Iterators.flatten below for ch in regularChannel push!(offsets, values(ch)) end - offsets = prepareOffsets(offsets, daq) - for (i, channel) in enumerate(regularChannel) - allSteps[channel] = offsets[i] + for (i, isPositive) in enumerate(quadrant) + push!(offsets, values(switchingChannel[i], isPositive)) end - end + quadrantSteps = prepareOffsets(offsets, daq) + for (i, channel) in enumerate(Iterators.flatten((regularChannel, switchingChannel))) + steps = get(allSteps, channel, []) + push!(steps, quadrantSteps[i]...) - # Add H-Bridge channel ignoring switches - hbridges = Dict{ProtocolOffsetElectricalChannel, Vector{Any}}() - for (ch, steps) in allSteps - offsetChannel = channel(daq, id(ch)) - if offsetChannel.range == HBRIDGE - hsteps = map(x-> ismissing(x) ? missing : level(offsetChannel.hbridge, x), steps) - hbridges[ch] = hsteps + # If not at the end then add missing + if comb != 2^length(switchingChannel)-1 + push!(steps, missing) + end + + allSteps[channel] = steps end end - # Prepare divider without h-bridge switch pauses - numSteps = length(first(allSteps)[2]) + hbridges = prepareHBridgeLevels(allSteps, daq) + numOffsets = length(filter(!ismissing, first(allSteps)[2])) - df = lcm(dfDivider(cpy)) - divider = df * numOffsets - # H-Bridge switches are present - if numOffsets != length(numSteps) - #time = maxSwitchTime(offsetVector, daq) - #numSwitchPeriods = ceil(Int64, time/lcm(dfDivider(cpy))) + # TODO compute switching time + numSwitchPeriods = 100 + divider = df * (numSwitchPeriods + numOffsets) + # Add numSwitchPeriods 0 for every missing value - numSwitchPeriods = 100 - divider = df * (numSwitchPeriods + numOffsets) - # Add numSwitchPeriods 0 for every missing value - for (ch, steps) in allSteps + # Set enable to false during hbridge switching + enableVec = Bool[] + foreach(x-> ismissing(x) ? push!(enableVec, fill(false, numSwitchPeriods)...) : push!(enableVec, true), first(allSteps)[2]) + + for (ch, steps) in allSteps + temp = [] + foreach(x-> ismissing(x) ? push!(temp, fill(zero(eltype(steps[1])), numSwitchPeriods)...) : push!(temp, x), steps) + allSteps[ch] = temp + enables[ch] = enableVec + + # Add next hbridge level for each missing value + if haskey(hbridges, ch) temp = [] - foreach(x-> ismissing(x) ? push!(temp, fill(zero(eltype(steps[1])), numSwitchPeriods)...) : push!(temp, x), steps) - allSteps[ch] = temp - - # Add next hbridge level for each missing value - if haskey(hbridges, ch) - temp = [] - for (i, x) in enumerate(hbridges[ch]) # Assumption no H-Bridge in last value - if ismissing(x) - # For the switch take the level for the value that comes after missing - push!(temp, fill(level(channel(daq, id(ch)).hbridge, hbridges[ch][i + 1]), numSwitchPeriods)...) - else - push!(temp, x) - end + for (i, x) in enumerate(hbridges[ch]) # Assumption no H-Bridge in last value + if ismissing(x) + # For the switch take the level for the value that comes after missing + push!(temp, fill(level(channel(daq, id(ch)).hbridge, hbridges[ch][i + 1]), numSwitchPeriods)...) + else + push!(temp, x) end - hbridges[ch] = temp end + hbridges[ch] = temp end + end + + # TODO set hbridge channels to abs + return allSteps, enables, hbridges +end - numOffsets = length(first(allSteps)[2]) - df = lcm(dfDivider(cpy)) - divider = df * numOffsets +function prepareOffsets(offsetVector::Vector{ProtocolOffsetElectricalChannel}, daq::RedPitayaDAQ, seq::Sequence) + allSteps = Dict{ProtocolOffsetElectricalChannel, Vector{Any}}() + enables = Dict{ProtocolOffsetElectricalChannel, Vector{Bool}}() + offsets = prepareOffsets(map(values, offsetVector), daq) + for (i, channel) in enumerate(offsetVector) + allSteps[channel] = offsets[i] + enables[channel] = fill(true, length(offsets[i])) end + #patchPermutation = collect(1:length(first(offsets))) + hbridges = prepareHBridgeLevels(allSteps, daq) + # TODO set hbridge channels to abs + return allSteps, enables, hbridges +end - # Generate actual StepwiseElectricalChannel +function prepareHBridgeLevels(allSteps, daq::RedPitayaDAQ) + hbridges = Dict{ProtocolOffsetElectricalChannel, Vector{Any}}() for (ch, steps) in allSteps - stepwise = StepwiseElectricalChannel(id = id(ch), divider = divider, values = identity.(steps), enable = fill(true, length(steps))) - fieldMap[ch][id(stepwise)] = stepwise - if haskey(hbridges, ch) - offsetChannel = channel(daq, id(ch)) - hbridgeChannel = offsetChannel.hbridge - hbridgeStepwise = StepwiseElectricalChannel(id = id(hbridgeChannel), divider = divider, values = identity.(hbridges[ch]), enable = fill(true, length(steps))) - fieldMap[ch][id(hbridgeChannel)] = hbridgeStepwise + offsetChannel = channel(daq, id(ch)) + if offsetChannel.range == HBRIDGE + hsteps = map(x-> ismissing(x) ? missing : level(offsetChannel.hbridge, x), steps) + hbridges[ch] = hsteps end end - - return cpy + return hbridges end function requireHBridge(offsetChannel::ProtocolOffsetElectricalChannel{T}, daq::RedPitayaDAQ) where T From f3074769c0470c29b01559e40c43ad25d8959ad7 Mon Sep 17 00:00:00 2001 From: nHackel Date: Fri, 13 Oct 2023 14:50:59 +0200 Subject: [PATCH 054/168] Implement sorting of MPS offsets --- src/Devices/DAQ/RedPitayaDAQ.jl | 34 +++++++-- src/Protocols/MPSMeasurementProtocol.jl | 94 +++++-------------------- 2 files changed, 46 insertions(+), 82 deletions(-) diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index 60ed36da..5871321b 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -473,10 +473,10 @@ function prepareProtocolSequences(base::Sequence, daq::RedPitayaDAQ) allSteps = nothing enables = nothing hbridges = nothing - if any.(x -> requireHBridge(x, daq), offsetVector) - allSteps, enables, hbridges = prepareHSwitchedOffsets(offsetVector, daq, cpy) + if any(x -> requireHBridge(x, daq), offsetVector) + allSteps, enables, hbridges, permutation = prepareHSwitchedOffsets(offsetVector, daq, cpy) else - allSteps, enables, hbridges = prepareOffsets(offsetVector, daq, cpy) + allSteps, enables, hbridges, permutation = prepareOffsets(offsetVector, daq, cpy) end numOffsets = length(first(allSteps)[2]) @@ -495,7 +495,7 @@ function prepareProtocolSequences(base::Sequence, daq::RedPitayaDAQ) end end - return cpy + return cpy, permutation end function prepareHSwitchedOffsets(offsetVector::Vector{ProtocolOffsetElectricalChannel}, daq::RedPitayaDAQ, seq::Sequence) @@ -540,7 +540,7 @@ function prepareHSwitchedOffsets(offsetVector::Vector{ProtocolOffsetElectricalCh # TODO compute switching time numSwitchPeriods = 100 - divider = df * (numSwitchPeriods + numOffsets) + #divider = df * (numSwitchPeriods + numOffsets) # Add numSwitchPeriods 0 for every missing value # Set enable to false during hbridge switching @@ -568,8 +568,28 @@ function prepareHSwitchedOffsets(offsetVector::Vector{ProtocolOffsetElectricalCh end end + # Construct permutation mask + # Generate offsets without permutation + offsets = Vector{Vector{Any}}() + for ch in offsetVector + push!(offsets, values(ch)) + end + offsets = hcat(prepareOffsets(offsets, daq)...) + # Map each offset combination to its original index + offsetDict = Dict{Vector, Int64}() + for (i, row) in enumerate(eachrow(offsets)) + offsetDict[row] = i + end + + # Sort patches according to their value in the index dictionary + permoffsets = hcat(map(x-> allSteps[x], offsetVector)...) + patchPermutation = sortperm(map(pair -> enableVec[pair[1]] ? offsetDict[identity(pair[2])] : typemax(Int64), enumerate(eachrow(permoffsets)))) + # Missing/switching patches are sorted to the end and need to be removed + patchPermutation = patchPermutation[1:end-length(filter(!identity, enableVec))] + + # TODO set hbridge channels to abs - return allSteps, enables, hbridges + return allSteps, enables, hbridges, patchPermutation end function prepareOffsets(offsetVector::Vector{ProtocolOffsetElectricalChannel}, daq::RedPitayaDAQ, seq::Sequence) @@ -583,7 +603,7 @@ function prepareOffsets(offsetVector::Vector{ProtocolOffsetElectricalChannel}, d #patchPermutation = collect(1:length(first(offsets))) hbridges = prepareHBridgeLevels(allSteps, daq) # TODO set hbridge channels to abs - return allSteps, enables, hbridges + return allSteps, enables, hbridges, 1:length(first(allSteps)[2]) end function prepareHBridgeLevels(allSteps, daq::RedPitayaDAQ) diff --git a/src/Protocols/MPSMeasurementProtocol.jl b/src/Protocols/MPSMeasurementProtocol.jl index a3bb14ff..a85e635b 100644 --- a/src/Protocols/MPSMeasurementProtocol.jl +++ b/src/Protocols/MPSMeasurementProtocol.jl @@ -19,14 +19,10 @@ Base.@kwdef mutable struct MPSMeasurementProtocolParams <: ProtocolParams saveTemperatureData::Bool = false "Sequence to measure" sequence::Union{Sequence, Nothing} = nothing + "Sort patches" + sortPatches::Bool = true # TODO: This is only for 1D MPS systems for now - "Start value of the MPS offset measurement. Overwrites parts of the sequence definition." - offsetStart::typeof(1.0u"T") = -0.012u"T" - "Stop value of the MPS offset measurement. Overwrites parts of the sequence definition." - offsetStop::typeof(1.0u"T") = 0.012u"T" - "Number of values of the MPS offset measurement. Overwrites parts of the sequence definition." - offsetNum::Integer = 101 "Number of periods per offset of the MPS offset measurement. Overwrites parts of the sequence definition." dfPeriodsPerOffset::Integer = 100 "Number of periods per offset which should be deleted. Acquired total number of periods is `dfPeriodsPerOffset + deletedDfPeriodsPerOffset`. Overwrites parts of the sequence definition." @@ -63,6 +59,7 @@ Base.@kwdef mutable struct MPSMeasurementProtocol <: Protocol protocolMeasState::Union{ProtocolMeasState, Nothing} = nothing sequences::Vector{Sequence} = Sequence[] + patchPermutation::Vector{Int64} = Int64[] bgMeas::Array{Float32, 4} = zeros(Float32,0,0,0,0) done::Bool = false @@ -82,9 +79,9 @@ function requiredDevices(protocol::MPSMeasurementProtocol) end function _init(protocol::MPSMeasurementProtocol) - if isnothing(sequence(protocol)) - throw(IllegalStateException("Protocol requires a sequence")) - end + #if isnothing(sequence(protocol)) + # throw(IllegalStateException("Protocol requires a sequence")) + #end protocol.done = false protocol.cancelled = false protocol.finishAcknowledged = false @@ -97,9 +94,11 @@ function _init(protocol::MPSMeasurementProtocol) protocol.protocolMeasState = ProtocolMeasState() try - protocol.sequences = prepareProtocolSequences(protocol.params.seqeuence, getDAQ(scanner(protocol))) + seq, perm = prepareProtocolSequences(protocol.params.sequence, getDAQ(scanner(protocol))) + protocol.sequences = [seq] + protocol.patchPermutation = perm catch e - throw(ProtocolConfigurationError(e)) + throw(e) end return nothing @@ -126,68 +125,9 @@ function enterExecute(protocol::MPSMeasurementProtocol) protocol.finishAcknowledged = false end -function getRelevantDfDivider(protocol::MPSMeasurementProtocol) - sequence_ = sequence(protocol) - - divider = nothing - offsetFieldIdx = nothing - offsetChannelIdx = nothing - for (fieldIdx, field) in enumerate(fields(sequence_)) - for (channelIdx, channel) in enumerate(channels(field)) - if channel isa PeriodicElectricalChannel - @warn "The protocol currently always uses the first component of $(id(channel)) for determining the divider." - divider = channel.components[1].divider - elseif channel isa ContinuousElectricalChannel - offsetFieldIdx = fieldIdx - offsetChannelIdx = channelIdx - end - end - end - - if isnothing(divider) - throw(ProtocolConfigurationError("The sequence `$(name(sequence_))` for the protocol `$(name(protocol))` does not define a PeriodicElectricalChannel and thus a divider.")) - end - - if isnothing(offsetFieldIdx) || isnothing(offsetChannelIdx) - throw(ProtocolConfigurationError("The sequence `$(name(sequence_))` for the protocol `$(name(protocol))` does not define a ContinuousElectricalChannel and thus an offset field description.")) - end - - return divider, offsetFieldIdx, offsetChannelIdx -end - -function createOffsetChannel(protocol::MPSMeasurementProtocol; deletedPeriodsPerOffset=protocol.params.deletedDfPeriodsPerOffset) - sequence_ = sequence(protocol) - - divider, offsetFieldIdx, offsetChannelIdx = getRelevantDfDivider(protocol) - - stepDivider = divider * (protocol.params.dfPeriodsPerOffset + deletedPeriodsPerOffset) - offsetDivider = stepDivider * protocol.params.offsetNum - - chanOffset = (protocol.params.offsetStop + protocol.params.offsetStart) / 2 - amplitude = abs(protocol.params.offsetStop - protocol.params.offsetStart) / 2 - - oldChannel = sequence_.fields[offsetFieldIdx].channels[offsetChannelIdx] - newChannel = ContinuousElectricalChannel(id=oldChannel.id, dividerSteps=stepDivider, divider=offsetDivider, amplitude=amplitude, phase=oldChannel.phase, offset=chanOffset, waveform=WAVEFORM_SAWTOOTH_RISING) - - @info "Values used for the creation of the offset channel" divider stepDivider offsetDivider chanOffset amplitude - - return newChannel, offsetFieldIdx, offsetChannelIdx -end - -function setupSequence(protocol::MPSMeasurementProtocol; deletedPeriodsPerOffset=protocol.params.deletedDfPeriodsPerOffset) - sequence_ = sequence(protocol) - newChannel, offsetFieldIdx, offsetChannelIdx = createOffsetChannel(protocol, deletedPeriodsPerOffset=deletedPeriodsPerOffset) - - @info "Offset values used for the measurement" MPIMeasurements.values(newChannel) - - sequence_.fields[offsetFieldIdx].channels[offsetChannelIdx] = newChannel - protocol.params.sequence = sequence_ -end - function _execute(protocol::MPSMeasurementProtocol) @debug "Measurement protocol started" - setupSequence(protocol) performMeasurement(protocol) put!(protocol.biChannel, FinishedNotificationEvent()) @@ -274,7 +214,7 @@ end function asyncMeasurement(protocol::MPSMeasurementProtocol) scanner_ = scanner(protocol) - sequence = protocol.params.sequence + sequence = protocol.sequences[1] daq = getDAQ(scanner_) deviceBuffer = DeviceBuffer[] if protocol.params.controlTx @@ -357,7 +297,11 @@ function handleEvent(protocol::MPSMeasurementProtocol, event::DatasetStoreStorag mdf = event.mdf data = read(protocol.protocolMeasState, MeasurementBuffer) - if protocol.params.deletedDfPeriodsPerOffset > 0 + if protocol.params.sortPatches + data = data[:, :, protocol.patchPermutation, :] + end + + #=if protocol.params.deletedDfPeriodsPerOffset > 0 numSamples_ = size(data, 1) numChannels_ = size(data, 2) numPeriods_ = size(data, 3) @@ -371,13 +315,13 @@ function handleEvent(protocol::MPSMeasurementProtocol, event::DatasetStoreStorag # Reset sequence since the info is used for the MDF setupSequence(protocol, deletedPeriodsPerOffset=0) - end + end=# isBGFrame = measIsBGFrame(protocol.protocolMeasState) drivefield = read(protocol.protocolMeasState, DriveFieldBuffer) appliedField = read(protocol.protocolMeasState, TxDAQControllerBuffer) temperature = read(protocol.protocolMeasState, TemperatureBuffer) - filename = saveasMDF(store, scanner, protocol.params.sequence, data, isBGFrame, mdf, drivefield = drivefield, temperatures = temperature, applied = appliedField) + filename = saveasMDF(store, scanner, protocol.sequences[1], data, isBGFrame, mdf, drivefield = drivefield, temperatures = temperature, applied = appliedField) @info "The measurement was saved at `$filename`." put!(protocol.biChannel, StorageSuccessEvent(filename)) end @@ -385,4 +329,4 @@ end protocolInteractivity(protocol::MPSMeasurementProtocol) = Interactive() protocolMDFStudyUse(protocol::MPSMeasurementProtocol) = UsingMDFStudy() -sequence(protocol::MPSMeasurementProtocol) = protocol.params.sequence \ No newline at end of file +sequence(protocol::MPSMeasurementProtocol) = protocol.sequences[1] \ No newline at end of file From b611f5e9dccf2e2c3f78635ceb4e02606a0b42cd Mon Sep 17 00:00:00 2001 From: nHackel Date: Fri, 13 Oct 2023 14:51:37 +0200 Subject: [PATCH 055/168] Hack to MDF acq storage s.t. MPS periods are correctly detected --- src/Protocols/Storage/MDF.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Protocols/Storage/MDF.jl b/src/Protocols/Storage/MDF.jl index 7b01ecf6..c30a844d 100644 --- a/src/Protocols/Storage/MDF.jl +++ b/src/Protocols/Storage/MDF.jl @@ -277,7 +277,7 @@ function fillMDFAcquisition(mdf::MDFv2InMemory, scanner::MPIScanner, sequence::S MPIFiles.acqNumAverages(mdf, acqNumAverages(sequence)) MPIFiles.acqNumFrames(mdf, length(measIsBackgroundFrame(mdf))) # important since we might have added BG frames - MPIFiles.acqNumPeriodsPerFrame(mdf, acqNumPeriodsPerFrame(sequence)) + MPIFiles.acqNumPeriodsPerFrame(mdf, size(measData(mdf), 3)) # Hacky to support cases where periods of data != periods of sequence offsetField_ = acqOffsetField(sequence) MPIFiles.acqOffsetField(mdf, isnothing(offsetField_) || !all(x-> x isa Unitful.MagneticFlux, offsetField_) ? nothing : ustrip.(u"T", offsetField_)) MPIFiles.acqStartTime(mdf, Dates.unix2datetime(time())) #seqCont.startTime) # TODO as parameter, start time from protocol From 0e1195e2de6dcb97d652c1b39010e2d553434bba Mon Sep 17 00:00:00 2001 From: nHackel Date: Fri, 13 Oct 2023 16:30:26 +0200 Subject: [PATCH 056/168] Add numPeriodsPerPatch to MPS protocol --- src/Devices/DAQ/RedPitayaDAQ.jl | 30 +++++++++++++++-------- src/Protocols/MPSMeasurementProtocol.jl | 32 ++++++++++++------------- 2 files changed, 36 insertions(+), 26 deletions(-) diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index 5871321b..05f6fdd9 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -457,7 +457,7 @@ end # TODO add deadPeriods -function prepareProtocolSequences(base::Sequence, daq::RedPitayaDAQ) +function prepareProtocolSequences(base::Sequence, daq::RedPitayaDAQ; numPeriodsPerPatch::Int64 = 1) cpy = deepcopy(base) # Prepare offset sequences @@ -474,14 +474,24 @@ function prepareProtocolSequences(base::Sequence, daq::RedPitayaDAQ) enables = nothing hbridges = nothing if any(x -> requireHBridge(x, daq), offsetVector) - allSteps, enables, hbridges, permutation = prepareHSwitchedOffsets(offsetVector, daq, cpy) + allSteps, enables, hbridges, permutation = prepareHSwitchedOffsets(offsetVector, daq, cpy, numPeriodsPerPatch) else allSteps, enables, hbridges, permutation = prepareOffsets(offsetVector, daq, cpy) end numOffsets = length(first(allSteps)[2]) df = lcm(dfDivider(cpy)) - divider = df * numOffsets + # Increasing the divider instead of repeating values allows the user to tune offset length s.t. the RP can keep up + divider = df * numOffsets * numPeriodsPerPatch + + if numPeriodsPerPatch > 1 + updatedPermutation = Int64[] + for patch in permutation + start = numPeriodsPerPatch * (patch - 1) + 1 + push!(updatedPermutation, map(x->start + x, 0:numPeriodsPerPatch-1)...) + end + permutation = updatedPermutation + end # Generate actual StepwiseElectricalChannel for (ch, steps) in allSteps @@ -498,7 +508,7 @@ function prepareProtocolSequences(base::Sequence, daq::RedPitayaDAQ) return cpy, permutation end -function prepareHSwitchedOffsets(offsetVector::Vector{ProtocolOffsetElectricalChannel}, daq::RedPitayaDAQ, seq::Sequence) +function prepareHSwitchedOffsets(offsetVector::Vector{ProtocolOffsetElectricalChannel}, daq::RedPitayaDAQ, seq::Sequence, numPeriodsPerPatch) switchingIndices = findall(x->requireHBridge(x,daq), offsetVector) switchingChannel = offsetVector[switchingIndices] regularChannel = isempty(switchingChannel) ? offsetVector : offsetVector[filter(!in(switchingIndices), 1:length(offsetVector))] @@ -536,12 +546,11 @@ function prepareHSwitchedOffsets(offsetVector::Vector{ProtocolOffsetElectricalCh hbridges = prepareHBridgeLevels(allSteps, daq) - numOffsets = length(filter(!ismissing, first(allSteps)[2])) - - # TODO compute switching time - numSwitchPeriods = 100 - #divider = df * (numSwitchPeriods + numOffsets) - # Add numSwitchPeriods 0 for every missing value + # Compute switch timing, assumption patch is held for df * numPeriodsPerPatch + df = lcm(dfDivider(seq)) * numPeriodsPerPatch + deadTimes = map(x-> x.hbridge.deadTime, filter(x-> x.range == HBRIDGE, map(x->channel(daq, id(x)), offsetVector))) + maxTime = maximum(map(ustrip, deadTimes)) + numSwitchPeriods = Int64(ceil(maxTime/(1/df))) # Set enable to false during hbridge switching enableVec = Bool[] @@ -549,6 +558,7 @@ function prepareHSwitchedOffsets(offsetVector::Vector{ProtocolOffsetElectricalCh for (ch, steps) in allSteps temp = [] + # Add numSwitchPeriods 0 for every missing value foreach(x-> ismissing(x) ? push!(temp, fill(zero(eltype(steps[1])), numSwitchPeriods)...) : push!(temp, x), steps) allSteps[ch] = temp enables[ch] = enableVec diff --git a/src/Protocols/MPSMeasurementProtocol.jl b/src/Protocols/MPSMeasurementProtocol.jl index a85e635b..dc2aad61 100644 --- a/src/Protocols/MPSMeasurementProtocol.jl +++ b/src/Protocols/MPSMeasurementProtocol.jl @@ -94,7 +94,7 @@ function _init(protocol::MPSMeasurementProtocol) protocol.protocolMeasState = ProtocolMeasState() try - seq, perm = prepareProtocolSequences(protocol.params.sequence, getDAQ(scanner(protocol))) + seq, perm = prepareProtocolSequences(protocol.params.sequence, getDAQ(scanner(protocol)); numPeriodsPerPatch = protocol.params.dfPeriodsPerOffset) protocol.sequences = [seq] protocol.patchPermutation = perm catch e @@ -301,21 +301,21 @@ function handleEvent(protocol::MPSMeasurementProtocol, event::DatasetStoreStorag data = data[:, :, protocol.patchPermutation, :] end - #=if protocol.params.deletedDfPeriodsPerOffset > 0 - numSamples_ = size(data, 1) - numChannels_ = size(data, 2) - numPeriods_ = size(data, 3) - numFrames_ = size(data, 4) - - numPeriodsPerOffset_ = div(numPeriods_, protocol.params.offsetNum) - - data = reshape(data, (numSamples_, numChannels_, numPeriodsPerOffset_, protocol.params.offsetNum, numFrames_)) - data = data[:, :, protocol.params.deletedDfPeriodsPerOffset+1:end, :, :] # Kick out first N periods - data = reshape(data, (numSamples_, numChannels_, :, numFrames_)) - - # Reset sequence since the info is used for the MDF - setupSequence(protocol, deletedPeriodsPerOffset=0) - end=# + #if protocol.params.deletedDfPeriodsPerOffset > 0 + # numSamples_ = size(data, 1) + # numChannels_ = size(data, 2) + # numPeriods_ = size(data, 3) + # numFrames_ = size(data, 4) +# + # numPeriodsPerOffset_ = div(numPeriods_, protocol.params.offsetNum) +# + # data = reshape(data, (numSamples_, numChannels_, numPeriodsPerOffset_, protocol.params.offsetNum, numFrames_)) + # data = data[:, :, protocol.params.deletedDfPeriodsPerOffset+1:end, :, :] # Kick out first N periods + # data = reshape(data, (numSamples_, numChannels_, :, numFrames_)) +# + # # Reset sequence since the info is used for the MDF + # setupSequence(protocol, deletedPeriodsPerOffset=0) + #end isBGFrame = measIsBGFrame(protocol.protocolMeasState) drivefield = read(protocol.protocolMeasState, DriveFieldBuffer) From e1e71a8d22b3ef3d8e9bf2c61b60aa1e81a91f3b Mon Sep 17 00:00:00 2001 From: nHackel Date: Fri, 13 Oct 2023 16:55:58 +0200 Subject: [PATCH 057/168] Readd dropping of df periods --- src/Protocols/MPSMeasurementProtocol.jl | 29 +++++++++++-------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/Protocols/MPSMeasurementProtocol.jl b/src/Protocols/MPSMeasurementProtocol.jl index dc2aad61..8fa9ddc5 100644 --- a/src/Protocols/MPSMeasurementProtocol.jl +++ b/src/Protocols/MPSMeasurementProtocol.jl @@ -24,7 +24,7 @@ Base.@kwdef mutable struct MPSMeasurementProtocolParams <: ProtocolParams # TODO: This is only for 1D MPS systems for now "Number of periods per offset of the MPS offset measurement. Overwrites parts of the sequence definition." - dfPeriodsPerOffset::Integer = 100 + dfPeriodsPerOffset::Integer = 2 "Number of periods per offset which should be deleted. Acquired total number of periods is `dfPeriodsPerOffset + deletedDfPeriodsPerOffset`. Overwrites parts of the sequence definition." deletedDfPeriodsPerOffset::Integer = 1 end @@ -301,21 +301,18 @@ function handleEvent(protocol::MPSMeasurementProtocol, event::DatasetStoreStorag data = data[:, :, protocol.patchPermutation, :] end - #if protocol.params.deletedDfPeriodsPerOffset > 0 - # numSamples_ = size(data, 1) - # numChannels_ = size(data, 2) - # numPeriods_ = size(data, 3) - # numFrames_ = size(data, 4) -# - # numPeriodsPerOffset_ = div(numPeriods_, protocol.params.offsetNum) -# - # data = reshape(data, (numSamples_, numChannels_, numPeriodsPerOffset_, protocol.params.offsetNum, numFrames_)) - # data = data[:, :, protocol.params.deletedDfPeriodsPerOffset+1:end, :, :] # Kick out first N periods - # data = reshape(data, (numSamples_, numChannels_, :, numFrames_)) -# - # # Reset sequence since the info is used for the MDF - # setupSequence(protocol, deletedPeriodsPerOffset=0) - #end + if protocol.params.deletedDfPeriodsPerOffset > 0 + numSamples_ = size(data, 1) + numChannels_ = size(data, 2) + numPeriods_ = size(data, 3) + numFrames_ = size(data, 4) + + periodsPerOffset = protocol.params.dfPeriodsPerOffset + + data = reshape(data, (numSamples_, numChannels_, periodsPerOffset, :, numFrames_)) + data = data[:, :, protocol.params.deletedDfPeriodsPerOffset+1:end, :, :] # Kick out first N periods + data = reshape(data, (numSamples_, numChannels_, :, numFrames_)) + end isBGFrame = measIsBGFrame(protocol.protocolMeasState) drivefield = read(protocol.protocolMeasState, DriveFieldBuffer) From 6c2561d62f96ee944aa5efdc3ebebfdc66501798 Mon Sep 17 00:00:00 2001 From: nHackel Date: Tue, 17 Oct 2023 11:04:16 +0200 Subject: [PATCH 058/168] =?UTF-8?q?Correct=20computation=20of=20h-bridge?= =?UTF-8?q?=20pause=20f=C3=BCr=20periodsPerPatch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Devices/DAQ/RedPitayaDAQ.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index 05f6fdd9..b810af8e 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -546,11 +546,11 @@ function prepareHSwitchedOffsets(offsetVector::Vector{ProtocolOffsetElectricalCh hbridges = prepareHBridgeLevels(allSteps, daq) - # Compute switch timing, assumption patch is held for df * numPeriodsPerPatch - df = lcm(dfDivider(seq)) * numPeriodsPerPatch + # Compute switch timing, assumption step is held for df * numPeriodsPerPatch + stepfreq = 125e6/lcm(dfDivider(seq))/numPeriodsPerPatch deadTimes = map(x-> x.hbridge.deadTime, filter(x-> x.range == HBRIDGE, map(x->channel(daq, id(x)), offsetVector))) maxTime = maximum(map(ustrip, deadTimes)) - numSwitchPeriods = Int64(ceil(maxTime/(1/df))) + numSwitchPeriods = Int64(ceil(maxTime/(1/stepfreq))) # Set enable to false during hbridge switching enableVec = Bool[] From 94a435f35553c6d6a0570e55c3441e99e5cdb209 Mon Sep 17 00:00:00 2001 From: nHackel Date: Tue, 17 Oct 2023 11:21:07 +0200 Subject: [PATCH 059/168] Take abs for h-bridge channel values --- src/Devices/DAQ/RedPitayaDAQ.jl | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index b810af8e..31848727 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -456,7 +456,6 @@ function getTiming(daq::RedPitayaDAQ) end -# TODO add deadPeriods function prepareProtocolSequences(base::Sequence, daq::RedPitayaDAQ; numPeriodsPerPatch::Int64 = 1) cpy = deepcopy(base) @@ -597,22 +596,30 @@ function prepareHSwitchedOffsets(offsetVector::Vector{ProtocolOffsetElectricalCh # Missing/switching patches are sorted to the end and need to be removed patchPermutation = patchPermutation[1:end-length(filter(!identity, enableVec))] + # Remove (negative) sign from all channels with an h-bridge + for (ch, steps) in filter(x->haskey(hbridges, x[1]), allSteps) + allSteps[ch] = abs.(steps) + end - # TODO set hbridge channels to abs return allSteps, enables, hbridges, patchPermutation end function prepareOffsets(offsetVector::Vector{ProtocolOffsetElectricalChannel}, daq::RedPitayaDAQ, seq::Sequence) allSteps = Dict{ProtocolOffsetElectricalChannel, Vector{Any}}() enables = Dict{ProtocolOffsetElectricalChannel, Vector{Bool}}() + offsets = prepareOffsets(map(values, offsetVector), daq) for (i, channel) in enumerate(offsetVector) allSteps[channel] = offsets[i] enables[channel] = fill(true, length(offsets[i])) end - #patchPermutation = collect(1:length(first(offsets))) + hbridges = prepareHBridgeLevels(allSteps, daq) - # TODO set hbridge channels to abs + # Remove (negative) sign from all channels with an h-bridge + for (ch, steps) in filter(x->haskey(hbridges, x[1]), allSteps) + allSteps[ch] = abs.(steps) + end + return allSteps, enables, hbridges, 1:length(first(allSteps)[2]) end From cd489f53e29de03cbb8cf2e90eab98b174de8426 Mon Sep 17 00:00:00 2001 From: nHackel Date: Fri, 20 Oct 2023 17:01:42 +0200 Subject: [PATCH 060/168] Init work on deadPeriods per Offset Channel --- src/Devices/DAQ/RedPitayaDAQ.jl | 123 ++++++++++++++++++++++++++++---- 1 file changed, 108 insertions(+), 15 deletions(-) diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index 31848727..c653232f 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -35,6 +35,8 @@ Base.@kwdef struct RedPitayaLUTChannelParams <: TxChannelParams calibration::Union{typeof(1.0u"V/T"), typeof(1.0u"V/A"), Nothing} = nothing range::TxValueRange = BOTH hbridge::Union{DAQHBridge, Nothing} = nothing + switchTime::typeof(1.0u"s") = 0.0u"s" + switchEnable::Bool = true end "Create the params struct from a dict. Typically called during scanner instantiation." @@ -95,6 +97,14 @@ function createDAQChannels(::Type{RedPitayaDAQParams}, dict::Dict{String, Any}) if haskey(value, "range") splattingDict[:range] = value["range"] end + + if haskey(value, "switchTime") + splattingDict[:switchTime] = uparse(value["switchTime"]) + end + + if haskey(value, "switchStrategy") + splattingDict[:switchStrategy] = value["switchStrategy"] + end channels[key] = RedPitayaLUTChannelParams(;splattingDict...) end end @@ -468,14 +478,18 @@ function prepareProtocolSequences(base::Sequence, daq::RedPitayaDAQ; numPeriodsP fieldMap[channel] = field end end + + # Compute step duration, assumption step is held for df * numPeriodsPerPatch + stepfreq = 125e6/lcm(dfDivider(cpy))/numPeriodsPerPatch + stepduration = (1/stepfreq) allSteps = nothing enables = nothing hbridges = nothing if any(x -> requireHBridge(x, daq), offsetVector) - allSteps, enables, hbridges, permutation = prepareHSwitchedOffsets(offsetVector, daq, cpy, numPeriodsPerPatch) + allSteps, enables, hbridges, permutation = prepareHSwitchedOffsets(offsetVector, daq, cpy, stepduration) else - allSteps, enables, hbridges, permutation = prepareOffsets(offsetVector, daq, cpy) + allSteps, enables, hbridges, permutation = prepareOffsets(offsetVector, daq, cpy, stepduration) end numOffsets = length(first(allSteps)[2]) @@ -507,12 +521,12 @@ function prepareProtocolSequences(base::Sequence, daq::RedPitayaDAQ; numPeriodsP return cpy, permutation end -function prepareHSwitchedOffsets(offsetVector::Vector{ProtocolOffsetElectricalChannel}, daq::RedPitayaDAQ, seq::Sequence, numPeriodsPerPatch) +function prepareHSwitchedOffsets(offsetVector::Vector{ProtocolOffsetElectricalChannel}, daq::RedPitayaDAQ, seq::Sequence, stepduration) switchingIndices = findall(x->requireHBridge(x,daq), offsetVector) switchingChannel = offsetVector[switchingIndices] regularChannel = isempty(switchingChannel) ? offsetVector : offsetVector[filter(!in(switchingIndices), 1:length(offsetVector))] allSteps = Dict{ProtocolOffsetElectricalChannel, Vector{Any}}() - enables = Dict{ProtocolOffsetElectricalChannel, Vector{Bool}}() + allEnables = Dict{ProtocolOffsetElectricalChannel, Vector{Union{Bool, Missing}}}() # Prepare values with missing for h-bridge switch # Permute patches by considering quadrants. Use lower bits of number to encode permutations @@ -528,39 +542,47 @@ function prepareHSwitchedOffsets(offsetVector::Vector{ProtocolOffsetElectricalCh for (i, isPositive) in enumerate(quadrant) push!(offsets, values(switchingChannel[i], isPositive)) end - quadrantSteps = prepareOffsets(offsets, daq) - for (i, channel) in enumerate(Iterators.flatten((regularChannel, switchingChannel))) + orderedChannel = collect(Iterators.flatten((regularChannel, switchingChannel))) + quadrantSteps, switchEnables, perm = prepareOffsetSwitches(offsets, orderedChannel, daq, stepduration) + + for (i, channel) in enumerate(orderedChannel[perm]) steps = get(allSteps, channel, []) + enables = get(allEnables, channel, Union{Bool, Missing}[]) push!(steps, quadrantSteps[i]...) + push!(enables, switchEnables[i]...) # If not at the end then add missing if comb != 2^length(switchingChannel)-1 push!(steps, missing) + push!(enables, missing) end allSteps[channel] = steps + allEnables[channel] = enables end end hbridges = prepareHBridgeLevels(allSteps, daq) # Compute switch timing, assumption step is held for df * numPeriodsPerPatch - stepfreq = 125e6/lcm(dfDivider(seq))/numPeriodsPerPatch deadTimes = map(x-> x.hbridge.deadTime, filter(x-> x.range == HBRIDGE, map(x->channel(daq, id(x)), offsetVector))) maxTime = maximum(map(ustrip, deadTimes)) - numSwitchPeriods = Int64(ceil(maxTime/(1/stepfreq))) + numSwitchPeriods = Int64(ceil(maxTime/stepduration)) # Set enable to false during hbridge switching - enableVec = Bool[] - foreach(x-> ismissing(x) ? push!(enableVec, fill(false, numSwitchPeriods)...) : push!(enableVec, true), first(allSteps)[2]) + for (ch, enables) in allEnables + temp = Bool[] + foreach(x-> ismissing(x) ? push!(temp, fill(false, numSwitchPeriods)...) : push!(temp, true), enables) + allEnables[ch] = temp + end + # Prepare H-Bridge pauses for (ch, steps) in allSteps temp = [] # Add numSwitchPeriods 0 for every missing value foreach(x-> ismissing(x) ? push!(temp, fill(zero(eltype(steps[1])), numSwitchPeriods)...) : push!(temp, x), steps) allSteps[ch] = temp - enables[ch] = enableVec # Add next hbridge level for each missing value if haskey(hbridges, ch) @@ -592,19 +614,19 @@ function prepareHSwitchedOffsets(offsetVector::Vector{ProtocolOffsetElectricalCh # Sort patches according to their value in the index dictionary permoffsets = hcat(map(x-> allSteps[x], offsetVector)...) - patchPermutation = sortperm(map(pair -> enableVec[pair[1]] ? offsetDict[identity(pair[2])] : typemax(Int64), enumerate(eachrow(permoffsets)))) + #patchPermutation = sortperm(map(pair -> enableVec[pair[1]] ? offsetDict[identity(pair[2])] : typemax(Int64), enumerate(eachrow(permoffsets)))) # Missing/switching patches are sorted to the end and need to be removed - patchPermutation = patchPermutation[1:end-length(filter(!identity, enableVec))] + #patchPermutation = patchPermutation[1:end-length(filter(!identity, enableVec))] # Remove (negative) sign from all channels with an h-bridge for (ch, steps) in filter(x->haskey(hbridges, x[1]), allSteps) allSteps[ch] = abs.(steps) end - return allSteps, enables, hbridges, patchPermutation + return allSteps, allEnables, hbridges, 1:length(first(allSteps)[2]) # patchPermutation end -function prepareOffsets(offsetVector::Vector{ProtocolOffsetElectricalChannel}, daq::RedPitayaDAQ, seq::Sequence) +function prepareOffsets(offsetVector::Vector{ProtocolOffsetElectricalChannel}, daq::RedPitayaDAQ, seq::Sequence, stepduration) allSteps = Dict{ProtocolOffsetElectricalChannel, Vector{Any}}() enables = Dict{ProtocolOffsetElectricalChannel, Vector{Bool}}() @@ -651,6 +673,77 @@ function requireHBridge(offsetChannel::ProtocolOffsetElectricalChannel{T}, daq:: end end +function prepareOffsetSwitches(offsets::Vector{Vector{T}}, channels::Vector{ProtocolOffsetElectricalChannel}, daq::RedPitayaDAQ, stepduration::Float64) where T + switchSteps = Dict{ProtocolOffsetElectricalChannel, Int64}() + for ch in channels + daqChannel = channel(daq, id(ch)) + if !iszero(daqChannel.switchTime) + switchSteps[ch] = Int64(ceil(ustrip(u"s", daqChannel.switchTime/stepduration))) + end + end + + if isempty(switchSteps) + return prepareOffsets(offsets, daq), Dict(ch => fill(true, length(first(offsets))), channels), 1:length(channels) + end + + @show switchSteps + perm = sortperm(map(x-> haskey(switchSteps, x) ? switchSteps[x] : 0, channels)) + + sortedOffsets = offsets[perm] + sortedChannels = channels[perm] + + sortedOffsetsWithPause = [] + sortedEnable = [] + for (i, ch) in enumerate(sortedChannels) + #values = repeat(sortedOffsets[i], inner = 1 + get(switchSteps, ch, 0)) + values = [[value] for value in sortedOffsets[i]] + enables = fill([true], length(values)) + + # Repeat each step + for (j, other) in enumerate(sortedChannels[1:i-1]) + if !haskey(switchSteps, other) + values = repeat.(values, inner = length(sortedOffsets[j])) + enables = repeat.(enables, inner = length(sortedOffsets[j])) + end + end + + tempValues = [] + tempEnables = [] + if !haskey(switchSteps, ch) + push!(tempValues, vcat(values...)...) + push!(tempEnables, vcat(enables...)...) + else + for (j, step) in enumerate(values) + push!(tempValues, fill(first(step), switchSteps[ch])...) + push!(tempValues, step...) + push!(tempEnables, fill(true, switchSteps[ch])...) + push!(tempEnables, enables[j]...) + end + end + values = tempValues + enables = tempEnables + + + for (j, other) in enumerate(sortedChannels[i+1:end]) + j = j + i + tempValues = values + tempEnables = enables + if haskey(switchSteps, other) + numSwitchSteps = switchSteps[other] + tempValues = pushfirst!(values, fill(first(sortedOffsets[i]), numSwitchSteps)...) + tempEnables = pushfirst!(enables, fill(false, numSwitchSteps)...) + end + values = repeat(tempValues, length(sortedOffsets[j])) + enables = repeat(tempEnables, length(sortedOffsets[j])) + end + + push!(sortedOffsetsWithPause, values) + push!(sortedEnable, enables) + end + + return sortedOffsetsWithPause, sortedEnable, perm +end + function prepareOffsets(offsetVectors::Vector{Vector{T}}, daq::RedPitayaDAQ) where T result = Vector{Vector{Any}}() inner = 1 From 0ab748e52502ee5c9fa3a6140d20954ca8b66af3 Mon Sep 17 00:00:00 2001 From: nHackel Date: Fri, 20 Oct 2023 18:54:58 +0200 Subject: [PATCH 061/168] Init working patch cutting for pmps @ IBI --- src/Devices/DAQ/RedPitayaDAQ.jl | 75 +++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 28 deletions(-) diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index c653232f..61d1a6bc 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -102,9 +102,10 @@ function createDAQChannels(::Type{RedPitayaDAQParams}, dict::Dict{String, Any}) splattingDict[:switchTime] = uparse(value["switchTime"]) end - if haskey(value, "switchStrategy") - splattingDict[:switchStrategy] = value["switchStrategy"] + if haskey(value, "switchEnable") + splattingDict[:switchEnable] = value["switchEnable"] end + channels[key] = RedPitayaLUTChannelParams(;splattingDict...) end end @@ -544,13 +545,16 @@ function prepareHSwitchedOffsets(offsetVector::Vector{ProtocolOffsetElectricalCh end orderedChannel = collect(Iterators.flatten((regularChannel, switchingChannel))) - quadrantSteps, switchEnables, perm = prepareOffsetSwitches(offsets, orderedChannel, daq, stepduration) + quadrantSteps, othersPause, perm = prepareOffsetSwitches(offsets, orderedChannel, daq, stepduration) - for (i, channel) in enumerate(orderedChannel[perm]) - steps = get(allSteps, channel, []) - enables = get(allEnables, channel, Union{Bool, Missing}[]) + for (i, ch) in enumerate(orderedChannel[perm]) + steps = get(allSteps, ch, []) + enables = get(allEnables, ch, Union{Bool, Missing}[]) push!(steps, quadrantSteps[i]...) - push!(enables, switchEnables[i]...) + #@show channel(daq, id(ch)) + #@show othersPause[i] + #@show map(x-> channel(daq, id(ch)).switchEnable ? true : !x, othersPause[i]) + push!(enables, map(x-> channel(daq, id(ch)).switchEnable ? true : !x, othersPause[i])...) # If not at the end then add missing if comb != 2^length(switchingChannel)-1 @@ -558,11 +562,16 @@ function prepareHSwitchedOffsets(offsetVector::Vector{ProtocolOffsetElectricalCh push!(enables, missing) end - allSteps[channel] = steps - allEnables[channel] = enables + allSteps[ch] = steps + allEnables[ch] = enables end end + #foreach(x->println(length(x)), values(allSteps)) + foreach(x->println(length(x)), values(allEnables)) + foreach(x->println(length(filter(y -> ismissing(y) ? false : y, x))), values(allEnables)) + + hbridges = prepareHBridgeLevels(allSteps, daq) # Compute switch timing, assumption step is held for df * numPeriodsPerPatch @@ -573,10 +582,14 @@ function prepareHSwitchedOffsets(offsetVector::Vector{ProtocolOffsetElectricalCh # Set enable to false during hbridge switching for (ch, enables) in allEnables temp = Bool[] - foreach(x-> ismissing(x) ? push!(temp, fill(false, numSwitchPeriods)...) : push!(temp, true), enables) + foreach(x-> ismissing(x) ? push!(temp, fill(false, numSwitchPeriods)...) : push!(temp, x), enables) allEnables[ch] = temp end + foreach(x->println(length(x)), values(allEnables)) + foreach(x->println(length(filter(y -> ismissing(y) ? false : y, x))), values(allEnables)) + + # Prepare H-Bridge pauses for (ch, steps) in allSteps temp = [] @@ -612,18 +625,25 @@ function prepareHSwitchedOffsets(offsetVector::Vector{ProtocolOffsetElectricalCh offsetDict[row] = i end + # Permutation Mask + enableMatrix = hcat(values(allEnables)...) + @show enableMatrix + #@show enableMatrix + permutationMask = map(all, eachrow(enableMatrix)) + @show length(permutationMask) + @show length(filter(!identity, permutationMask)) # Sort patches according to their value in the index dictionary permoffsets = hcat(map(x-> allSteps[x], offsetVector)...) - #patchPermutation = sortperm(map(pair -> enableVec[pair[1]] ? offsetDict[identity(pair[2])] : typemax(Int64), enumerate(eachrow(permoffsets)))) + patchPermutation = sortperm(map(pair -> permutationMask[pair[1]] ? offsetDict[identity(pair[2])] : typemax(Int64), enumerate(eachrow(permoffsets)))) # Missing/switching patches are sorted to the end and need to be removed - #patchPermutation = patchPermutation[1:end-length(filter(!identity, enableVec))] + patchPermutation = patchPermutation[1:end-length(filter(!identity, permutationMask))] # Remove (negative) sign from all channels with an h-bridge for (ch, steps) in filter(x->haskey(hbridges, x[1]), allSteps) allSteps[ch] = abs.(steps) end - return allSteps, allEnables, hbridges, 1:length(first(allSteps)[2]) # patchPermutation + return allSteps, allEnables, hbridges, patchPermutation end function prepareOffsets(offsetVector::Vector{ProtocolOffsetElectricalChannel}, daq::RedPitayaDAQ, seq::Sequence, stepduration) @@ -686,62 +706,61 @@ function prepareOffsetSwitches(offsets::Vector{Vector{T}}, channels::Vector{Prot return prepareOffsets(offsets, daq), Dict(ch => fill(true, length(first(offsets))), channels), 1:length(channels) end - @show switchSteps perm = sortperm(map(x-> haskey(switchSteps, x) ? switchSteps[x] : 0, channels)) sortedOffsets = offsets[perm] sortedChannels = channels[perm] sortedOffsetsWithPause = [] - sortedEnable = [] + sortedOthersPause = [] for (i, ch) in enumerate(sortedChannels) #values = repeat(sortedOffsets[i], inner = 1 + get(switchSteps, ch, 0)) values = [[value] for value in sortedOffsets[i]] - enables = fill([true], length(values)) + othersPause = fill([false], length(values)) # Repeat each step for (j, other) in enumerate(sortedChannels[1:i-1]) if !haskey(switchSteps, other) values = repeat.(values, inner = length(sortedOffsets[j])) - enables = repeat.(enables, inner = length(sortedOffsets[j])) + othersPause = repeat.(othersPause, inner = length(sortedOffsets[j])) end end tempValues = [] - tempEnables = [] + tempOthersPause = [] if !haskey(switchSteps, ch) push!(tempValues, vcat(values...)...) - push!(tempEnables, vcat(enables...)...) + push!(tempOthersPause, vcat(othersPause...)...) else for (j, step) in enumerate(values) push!(tempValues, fill(first(step), switchSteps[ch])...) push!(tempValues, step...) - push!(tempEnables, fill(true, switchSteps[ch])...) - push!(tempEnables, enables[j]...) + push!(tempOthersPause, fill(false, switchSteps[ch])...) + push!(tempOthersPause, othersPause[j]...) end end values = tempValues - enables = tempEnables - + othersPause = tempOthersPause for (j, other) in enumerate(sortedChannels[i+1:end]) j = j + i tempValues = values - tempEnables = enables + tempOthersPause = othersPause if haskey(switchSteps, other) numSwitchSteps = switchSteps[other] + @show numSwitchSteps tempValues = pushfirst!(values, fill(first(sortedOffsets[i]), numSwitchSteps)...) - tempEnables = pushfirst!(enables, fill(false, numSwitchSteps)...) + tempOthersPause = pushfirst!(othersPause, fill(true, numSwitchSteps)...) end values = repeat(tempValues, length(sortedOffsets[j])) - enables = repeat(tempEnables, length(sortedOffsets[j])) + othersPause = repeat(tempOthersPause, length(sortedOffsets[j])) end push!(sortedOffsetsWithPause, values) - push!(sortedEnable, enables) + push!(sortedOthersPause, othersPause) end - return sortedOffsetsWithPause, sortedEnable, perm + return sortedOffsetsWithPause, sortedOthersPause, perm end function prepareOffsets(offsetVectors::Vector{Vector{T}}, daq::RedPitayaDAQ) where T From 9b0219355c2921f0d53a5c1f2c18fa9266109788 Mon Sep 17 00:00:00 2001 From: nHackel Date: Fri, 27 Oct 2023 18:33:52 +0200 Subject: [PATCH 062/168] Attempt n-dimensional switch time optimization Missing first pause, testing and potentially smarter sorting to achieve optimal savings --- src/Devices/DAQ/RedPitayaDAQ.jl | 77 +++++++++++++-------------------- 1 file changed, 31 insertions(+), 46 deletions(-) diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index 61d1a6bc..bde73c03 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -551,9 +551,6 @@ function prepareHSwitchedOffsets(offsetVector::Vector{ProtocolOffsetElectricalCh steps = get(allSteps, ch, []) enables = get(allEnables, ch, Union{Bool, Missing}[]) push!(steps, quadrantSteps[i]...) - #@show channel(daq, id(ch)) - #@show othersPause[i] - #@show map(x-> channel(daq, id(ch)).switchEnable ? true : !x, othersPause[i]) push!(enables, map(x-> channel(daq, id(ch)).switchEnable ? true : !x, othersPause[i])...) # If not at the end then add missing @@ -627,15 +624,12 @@ function prepareHSwitchedOffsets(offsetVector::Vector{ProtocolOffsetElectricalCh # Permutation Mask enableMatrix = hcat(values(allEnables)...) - @show enableMatrix #@show enableMatrix permutationMask = map(all, eachrow(enableMatrix)) - @show length(permutationMask) - @show length(filter(!identity, permutationMask)) # Sort patches according to their value in the index dictionary permoffsets = hcat(map(x-> allSteps[x], offsetVector)...) patchPermutation = sortperm(map(pair -> permutationMask[pair[1]] ? offsetDict[identity(pair[2])] : typemax(Int64), enumerate(eachrow(permoffsets)))) - # Missing/switching patches are sorted to the end and need to be removed + # Pause/switching patches are sorted to the end and need to be removed patchPermutation = patchPermutation[1:end-length(filter(!identity, permutationMask))] # Remove (negative) sign from all channels with an h-bridge @@ -714,52 +708,43 @@ function prepareOffsetSwitches(offsets::Vector{Vector{T}}, channels::Vector{Prot sortedOffsetsWithPause = [] sortedOthersPause = [] for (i, ch) in enumerate(sortedChannels) - #values = repeat(sortedOffsets[i], inner = 1 + get(switchSteps, ch, 0)) - values = [[value] for value in sortedOffsets[i]] - othersPause = fill([false], length(values)) - - # Repeat each step - for (j, other) in enumerate(sortedChannels[1:i-1]) - if !haskey(switchSteps, other) - values = repeat.(values, inner = length(sortedOffsets[j])) - othersPause = repeat.(othersPause, inner = length(sortedOffsets[j])) + inner = isempty(sortedOffsetsWithPause) ? 1 : length(last(sortedOffsetsWithPause)) + offs = [repeat([value], inner = inner) for value in sortedOffsets[i]] + othersPause = [push!(repeat([true], inner = inner - 1), false) for value in sortedOffsets[i]] + + numSwitchSteps = get(switchSteps, ch, 0) + # Consider own pauses + if !iszero(numSwitchSteps) + for (k, level) in enumerate(offs[2:end]) + push!(level, fill(first(level), numSwitchSteps)...) + push!(othersPause[k + 1], fill(false, numSwitchSteps)...) end end - - tempValues = [] - tempOthersPause = [] - if !haskey(switchSteps, ch) - push!(tempValues, vcat(values...)...) - push!(tempOthersPause, vcat(othersPause...)...) - else - for (j, step) in enumerate(values) - push!(tempValues, fill(first(step), switchSteps[ch])...) - push!(tempValues, step...) - push!(tempOthersPause, fill(false, switchSteps[ch])...) - push!(tempOthersPause, othersPause[j]...) + offs = vcat(offs...) + othersPause = vcat(othersPause...) + + # Update other channels with own repetition number + for (j, otherOffs) in enumerate(sortedOffsetsWithPause) + # Add one pause + tempOffs = deepcopy(otherOffs) + tempOthersPause = deepcopy(sortedOthersPause[j]) + if !iszero(numSwitchSteps) + push!(tempOffs, fill(first(sortedOffsets[j]), numSwitchSteps)...) + push!(tempOthersPause, fill(true, numSwitchSteps)...) end - end - values = tempValues - othersPause = tempOthersPause - - for (j, other) in enumerate(sortedChannels[i+1:end]) - j = j + i - tempValues = values - tempOthersPause = othersPause - if haskey(switchSteps, other) - numSwitchSteps = switchSteps[other] - @show numSwitchSteps - tempValues = pushfirst!(values, fill(first(sortedOffsets[i]), numSwitchSteps)...) - tempOthersPause = pushfirst!(othersPause, fill(true, numSwitchSteps)...) - end - values = repeat(tempValues, length(sortedOffsets[j])) - othersPause = repeat(tempOthersPause, length(sortedOffsets[j])) + # Repeat n-1 times + tempOffs = repeat(tempOffs, outer = length(sortedOffsets[i]) - 1) + tempOthersPause = repeat(tempOthersPause, outer = length(sortedOffsets[i]) - 1) + # Add last repetition without a following pause + sortedOffsetsWithPause[j] = vcat(tempOffs, otherOffs) + sortedOthersPause[j] = vcat(tempOthersPause, otherOthersPause) end - push!(sortedOffsetsWithPause, values) - push!(sortedOthersPause, othersPause) + push!(sortedOffsetsWithPause, offs) end + # Add first pause + return sortedOffsetsWithPause, sortedOthersPause, perm end From fdda0f212dc7c19e669dc53606c4b42c86053605 Mon Sep 17 00:00:00 2001 From: nHackel Date: Fri, 3 Nov 2023 10:25:01 +0100 Subject: [PATCH 063/168] Add level switch pauses for mps seq. without hbridge switching --- src/Devices/DAQ/RedPitayaDAQ.jl | 43 +++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index bde73c03..7fdd226d 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -564,11 +564,6 @@ function prepareHSwitchedOffsets(offsetVector::Vector{ProtocolOffsetElectricalCh end end - #foreach(x->println(length(x)), values(allSteps)) - foreach(x->println(length(x)), values(allEnables)) - foreach(x->println(length(filter(y -> ismissing(y) ? false : y, x))), values(allEnables)) - - hbridges = prepareHBridgeLevels(allSteps, daq) # Compute switch timing, assumption step is held for df * numPeriodsPerPatch @@ -611,10 +606,7 @@ function prepareHSwitchedOffsets(offsetVector::Vector{ProtocolOffsetElectricalCh # Construct permutation mask # Generate offsets without permutation - offsets = Vector{Vector{Any}}() - for ch in offsetVector - push!(offsets, values(ch)) - end + offsets = map(values, offsetVector) offsets = hcat(prepareOffsets(offsets, daq)...) # Map each offset combination to its original index offsetDict = Dict{Vector, Int64}() @@ -624,7 +616,6 @@ function prepareHSwitchedOffsets(offsetVector::Vector{ProtocolOffsetElectricalCh # Permutation Mask enableMatrix = hcat(values(allEnables)...) - #@show enableMatrix permutationMask = map(all, eachrow(enableMatrix)) # Sort patches according to their value in the index dictionary permoffsets = hcat(map(x-> allSteps[x], offsetVector)...) @@ -644,11 +635,29 @@ function prepareOffsets(offsetVector::Vector{ProtocolOffsetElectricalChannel}, d allSteps = Dict{ProtocolOffsetElectricalChannel, Vector{Any}}() enables = Dict{ProtocolOffsetElectricalChannel, Vector{Bool}}() - offsets = prepareOffsets(map(values, offsetVector), daq) - for (i, channel) in enumerate(offsetVector) - allSteps[channel] = offsets[i] - enables[channel] = fill(true, length(offsets[i])) + offsets = map(values, offsetVector) + steps, othersPause, perm = prepareOffsetSwitches(offsets, offsetVector, daq, stepduration) + for (i, ch) in enumerate(offsetVector[perm]) + allSteps[ch] = steps[i] + enables[ch] = map(x-> channel(daq, id(ch)).switchEnable ? true : !x, othersPause[i]) + end + + # Construct permutation mask + # Map each offset combination to its original index + offsets = hcat(prepareOffsets(offsets, daq)...) + offsetDict = Dict{Vector, Int64}() + for (i, row) in enumerate(eachrow(offsets)) + offsetDict[row] = i end + + # Permutation Mask + enableMatrix = hcat(values(enables)...) + permutationMask = map(all, eachrow(enableMatrix)) + # Sort patches according to their value in the index dictionary + permoffsets = hcat(map(x-> allSteps[x], offsetVector)...) + patchPermutation = sortperm(map(pair -> permutationMask[pair[1]] ? offsetDict[identity(pair[2])] : typemax(Int64), enumerate(eachrow(permoffsets)))) + # Switching patches are sorted to the end and need to be removed + patchPermutation = patchPermutation[1:end-length(filter(!identity, permutationMask))] hbridges = prepareHBridgeLevels(allSteps, daq) # Remove (negative) sign from all channels with an h-bridge @@ -656,7 +665,7 @@ function prepareOffsets(offsetVector::Vector{ProtocolOffsetElectricalChannel}, d allSteps[ch] = abs.(steps) end - return allSteps, enables, hbridges, 1:length(first(allSteps)[2]) + return allSteps, enables, hbridges, patchPermutation end function prepareHBridgeLevels(allSteps, daq::RedPitayaDAQ) @@ -700,6 +709,7 @@ function prepareOffsetSwitches(offsets::Vector{Vector{T}}, channels::Vector{Prot return prepareOffsets(offsets, daq), Dict(ch => fill(true, length(first(offsets))), channels), 1:length(channels) end + # Sorting is not optimal, but allows us in the next step to consider pauses individually and not have to take max of all possible pauses at a point perm = sortperm(map(x-> haskey(switchSteps, x) ? switchSteps[x] : 0, channels)) sortedOffsets = offsets[perm] @@ -737,9 +747,10 @@ function prepareOffsetSwitches(offsets::Vector{Vector{T}}, channels::Vector{Prot tempOthersPause = repeat(tempOthersPause, outer = length(sortedOffsets[i]) - 1) # Add last repetition without a following pause sortedOffsetsWithPause[j] = vcat(tempOffs, otherOffs) - sortedOthersPause[j] = vcat(tempOthersPause, otherOthersPause) + sortedOthersPause[j] = vcat(tempOthersPause, sortedOthersPause[j]) end + push!(sortedOthersPause, othersPause) push!(sortedOffsetsWithPause, offs) end From 922a3b5265677dfe7b35bfcebcbe1739d7a2b9dc Mon Sep 17 00:00:00 2001 From: nHackel Date: Fri, 3 Nov 2023 10:36:22 +0100 Subject: [PATCH 064/168] MPS Protocol always remove "dead" patches --- src/Protocols/MPSMeasurementProtocol.jl | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/src/Protocols/MPSMeasurementProtocol.jl b/src/Protocols/MPSMeasurementProtocol.jl index 8fa9ddc5..022641bb 100644 --- a/src/Protocols/MPSMeasurementProtocol.jl +++ b/src/Protocols/MPSMeasurementProtocol.jl @@ -299,19 +299,10 @@ function handleEvent(protocol::MPSMeasurementProtocol, event::DatasetStoreStorag if protocol.params.sortPatches data = data[:, :, protocol.patchPermutation, :] - end - - if protocol.params.deletedDfPeriodsPerOffset > 0 - numSamples_ = size(data, 1) - numChannels_ = size(data, 2) - numPeriods_ = size(data, 3) - numFrames_ = size(data, 4) - - periodsPerOffset = protocol.params.dfPeriodsPerOffset - - data = reshape(data, (numSamples_, numChannels_, periodsPerOffset, :, numFrames_)) - data = data[:, :, protocol.params.deletedDfPeriodsPerOffset+1:end, :, :] # Kick out first N periods - data = reshape(data, (numSamples_, numChannels_, :, numFrames_)) + else + # Just remove "dead" patches + validPatches = filter(in(protocol.patchPermutation), collect(1:size(data, 3))) + data = data[:, :, validPatches, :] end isBGFrame = measIsBGFrame(protocol.protocolMeasState) From e255cd4cb34872c16e7e1d88fffddae6b6ced850 Mon Sep 17 00:00:00 2001 From: nHackel Date: Fri, 3 Nov 2023 11:07:53 +0100 Subject: [PATCH 065/168] Added missing first pause --- src/Devices/DAQ/RedPitayaDAQ.jl | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index 7fdd226d..c9e7e37c 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -755,6 +755,12 @@ function prepareOffsetSwitches(offsets::Vector{Vector{T}}, channels::Vector{Prot end # Add first pause + maxPause = maximum(values(switchSteps)) + for (i, ch) in enumerate(sortedChannels) + numSwitchSteps = get(switchSteps, ch, 0) + pushfirst!(sortedOffsetsWithPause[i], fill(first(sortedOffsetsWithPause[i]), maxPause)...) + pushfirst!(sortedOthersPause[i], vcat(fill(false, numSwitchSteps), fill(true, maxPause - numSwitchSteps))...) + end return sortedOffsetsWithPause, sortedOthersPause, perm end From f667c886c26c60eb93dabc3fe3677524f35d0001 Mon Sep 17 00:00:00 2001 From: nHackel Date: Fri, 3 Nov 2023 17:45:32 +0100 Subject: [PATCH 066/168] Storing of offsetFields and saving as SM for MPS --- src/Devices/DAQ/RedPitayaDAQ.jl | 18 +++++++-------- src/Protocols/MPSMeasurementProtocol.jl | 29 ++++++++++++++++++++----- src/Protocols/Storage/MDF.jl | 22 ++++++++++++++++++- 3 files changed, 52 insertions(+), 17 deletions(-) diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index c9e7e37c..b5af307c 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -488,9 +488,9 @@ function prepareProtocolSequences(base::Sequence, daq::RedPitayaDAQ; numPeriodsP enables = nothing hbridges = nothing if any(x -> requireHBridge(x, daq), offsetVector) - allSteps, enables, hbridges, permutation = prepareHSwitchedOffsets(offsetVector, daq, cpy, stepduration) + offsets, allSteps, enables, hbridges, permutation = prepareHSwitchedOffsets(offsetVector, daq, cpy, stepduration) else - allSteps, enables, hbridges, permutation = prepareOffsets(offsetVector, daq, cpy, stepduration) + offsets, allSteps, enables, hbridges, permutation = prepareOffsets(offsetVector, daq, cpy, stepduration) end numOffsets = length(first(allSteps)[2]) @@ -505,6 +505,7 @@ function prepareProtocolSequences(base::Sequence, daq::RedPitayaDAQ; numPeriodsP push!(updatedPermutation, map(x->start + x, 0:numPeriodsPerPatch-1)...) end permutation = updatedPermutation + offsets = repeat(offsets, inner = Tuple([i == 1 ? numPeriodsPerPatch : 1 for i = 1:ndims(offsets)])) end # Generate actual StepwiseElectricalChannel @@ -519,7 +520,7 @@ function prepareProtocolSequences(base::Sequence, daq::RedPitayaDAQ; numPeriodsP end end - return cpy, permutation + return cpy, permutation, offsets end function prepareHSwitchedOffsets(offsetVector::Vector{ProtocolOffsetElectricalChannel}, daq::RedPitayaDAQ, seq::Sequence, stepduration) @@ -578,10 +579,6 @@ function prepareHSwitchedOffsets(offsetVector::Vector{ProtocolOffsetElectricalCh allEnables[ch] = temp end - foreach(x->println(length(x)), values(allEnables)) - foreach(x->println(length(filter(y -> ismissing(y) ? false : y, x))), values(allEnables)) - - # Prepare H-Bridge pauses for (ch, steps) in allSteps temp = [] @@ -628,7 +625,7 @@ function prepareHSwitchedOffsets(offsetVector::Vector{ProtocolOffsetElectricalCh allSteps[ch] = abs.(steps) end - return allSteps, allEnables, hbridges, patchPermutation + return permoffsets, allSteps, allEnables, hbridges, patchPermutation end function prepareOffsets(offsetVector::Vector{ProtocolOffsetElectricalChannel}, daq::RedPitayaDAQ, seq::Sequence, stepduration) @@ -665,7 +662,7 @@ function prepareOffsets(offsetVector::Vector{ProtocolOffsetElectricalChannel}, d allSteps[ch] = abs.(steps) end - return allSteps, enables, hbridges, patchPermutation + return permoffsets, allSteps, enables, hbridges, patchPermutation end function prepareHBridgeLevels(allSteps, daq::RedPitayaDAQ) @@ -706,7 +703,8 @@ function prepareOffsetSwitches(offsets::Vector{Vector{T}}, channels::Vector{Prot end if isempty(switchSteps) - return prepareOffsets(offsets, daq), Dict(ch => fill(true, length(first(offsets))), channels), 1:length(channels) + offsets = prepareOffsets(offsets, daq) + return offsets, [fill(true, length(first(offsets))) for ch in channels], 1:length(channels) end # Sorting is not optimal, but allows us in the next step to consider pauses individually and not have to take max of all possible pauses at a point diff --git a/src/Protocols/MPSMeasurementProtocol.jl b/src/Protocols/MPSMeasurementProtocol.jl index 022641bb..1d265781 100644 --- a/src/Protocols/MPSMeasurementProtocol.jl +++ b/src/Protocols/MPSMeasurementProtocol.jl @@ -21,6 +21,8 @@ Base.@kwdef mutable struct MPSMeasurementProtocolParams <: ProtocolParams sequence::Union{Sequence, Nothing} = nothing "Sort patches" sortPatches::Bool = true + "Flag if the measurement should be saved as a system matrix or not" + saveAsSystemMatrix::Bool = true # TODO: This is only for 1D MPS systems for now "Number of periods per offset of the MPS offset measurement. Overwrites parts of the sequence definition." @@ -58,7 +60,8 @@ Base.@kwdef mutable struct MPSMeasurementProtocol <: Protocol seqMeasState::Union{SequenceMeasState, Nothing} = nothing protocolMeasState::Union{ProtocolMeasState, Nothing} = nothing - sequences::Vector{Sequence} = Sequence[] + sequence::Union{Sequence, Nothing} = nothing + offsetfields::Union{Matrix{Float64}, Nothing} = nothing patchPermutation::Vector{Int64} = Int64[] bgMeas::Array{Float32, 4} = zeros(Float32,0,0,0,0) @@ -94,9 +97,10 @@ function _init(protocol::MPSMeasurementProtocol) protocol.protocolMeasState = ProtocolMeasState() try - seq, perm = prepareProtocolSequences(protocol.params.sequence, getDAQ(scanner(protocol)); numPeriodsPerPatch = protocol.params.dfPeriodsPerOffset) - protocol.sequences = [seq] + seq, perm, offsets = prepareProtocolSequences(protocol.params.sequence, getDAQ(scanner(protocol)); numPeriodsPerPatch = protocol.params.dfPeriodsPerOffset) + protocol.sequence = seq protocol.patchPermutation = perm + protocol.offsetfields = ustrip.(u"T", offsets) # TODO make robust catch e throw(e) end @@ -214,7 +218,7 @@ end function asyncMeasurement(protocol::MPSMeasurementProtocol) scanner_ = scanner(protocol) - sequence = protocol.sequences[1] + sequence = protocol.sequence daq = getDAQ(scanner_) deviceBuffer = DeviceBuffer[] if protocol.params.controlTx @@ -296,20 +300,33 @@ function handleEvent(protocol::MPSMeasurementProtocol, event::DatasetStoreStorag scanner = protocol.scanner mdf = event.mdf data = read(protocol.protocolMeasState, MeasurementBuffer) + offsets = protocol.offsetfields if protocol.params.sortPatches data = data[:, :, protocol.patchPermutation, :] + offsets = offsets[protocol.patchPermutation, :] else # Just remove "dead" patches validPatches = filter(in(protocol.patchPermutation), collect(1:size(data, 3))) data = data[:, :, validPatches, :] + offsets = offsets[validPatches, :] end isBGFrame = measIsBGFrame(protocol.protocolMeasState) drivefield = read(protocol.protocolMeasState, DriveFieldBuffer) appliedField = read(protocol.protocolMeasState, TxDAQControllerBuffer) temperature = read(protocol.protocolMeasState, TemperatureBuffer) - filename = saveasMDF(store, scanner, protocol.sequences[1], data, isBGFrame, mdf, drivefield = drivefield, temperatures = temperature, applied = appliedField) + + + filename = nothing + if protocol.params.saveAsSystemMatrix + isBGFrame = repeat(isBGFrame, inner = div(size(data, 3), protocol.params.dfPeriodsPerOffset)) + data = reshape(data, size(data, 1), size(data, 2), protocol.params.dfPeriodsPerOffset, :) + offsets = reshape(offsets, protocol.params.dfPeriodsPerOffset, :, size(offsets, 2))[1, :, :] # All periods in one frame (should) have same offset + filename = saveasMDF(store, scanner, protocol.sequence, data, offsets, isBGFrame, mdf, storeAsSystemMatrix=protocol.params.saveAsSystemMatrix, drivefield = drivefield, temperatures = temperature, applied = appliedField) + else + filename = saveasMDF(store, scanner, protocol.sequence, data, isBGFrame, mdf, drivefield = drivefield, temperatures = temperature, applied = appliedField) + end @info "The measurement was saved at `$filename`." put!(protocol.biChannel, StorageSuccessEvent(filename)) end @@ -317,4 +334,4 @@ end protocolInteractivity(protocol::MPSMeasurementProtocol) = Interactive() protocolMDFStudyUse(protocol::MPSMeasurementProtocol) = UsingMDFStudy() -sequence(protocol::MPSMeasurementProtocol) = protocol.sequences[1] \ No newline at end of file +sequence(protocol::MPSMeasurementProtocol) = protocol.sequence \ No newline at end of file diff --git a/src/Protocols/Storage/MDF.jl b/src/Protocols/Storage/MDF.jl index c30a844d..4e0113da 100644 --- a/src/Protocols/Storage/MDF.jl +++ b/src/Protocols/Storage/MDF.jl @@ -58,7 +58,7 @@ end function MPIFiles.saveasMDF(store::DatasetStore, scanner::MPIScanner, sequence::Sequence, data::Array{Float32,4}, - positions::Positions, isBackgroundFrame::Vector{Bool}, mdf::MDFv2InMemory; storeAsSystemMatrix::Bool = false, deltaSampleSize::Union{Vector{typeof(1.0u"m")}, Nothing} = nothing, temperatures::Union{Array{Float32}, Nothing}=nothing, drivefield::Union{Array{ComplexF64}, Nothing}=nothing, applied::Union{Array{ComplexF64}, Nothing}=nothing) + positions::Union{Positions, Matrix}, isBackgroundFrame::Vector{Bool}, mdf::MDFv2InMemory; storeAsSystemMatrix::Bool = false, deltaSampleSize::Union{Vector{typeof(1.0u"m")}, Nothing} = nothing, temperatures::Union{Array{Float32}, Nothing}=nothing, drivefield::Union{Array{ComplexF64}, Nothing}=nothing, applied::Union{Array{ComplexF64}, Nothing}=nothing) if storeAsSystemMatrix study = MPIFiles.getCalibStudy(store) @@ -135,6 +135,26 @@ function fillMDFCalibration(mdf::MDFv2InMemory, positions::GridPositions; deltaS return end +function fillMDFCalibration(mdf::MDFv2InMemory, offsetFields::Union{Matrix, Nothing}; deltaSampleSize::Union{Vector{typeof(1.0u"m")}, Nothing} = nothing) + + # /calibration/ subgroup + + if !isnothing(deltaSampleSize) + deltaSampleSize = Float64.(ustrip.(uconvert.(Unitful.m, deltaSampleSize))) : nothing + end + + method = "hybrid" + order = "xyz" + + mdf.calibration = MDFv2Calibration(; + deltaSampleSize = deltaSampleSize, + method = method, + offsetFields = offsetFields, + order = order, + ) + + return +end fillMDFCalibration(mdf::MDFv2InMemory, positions::Positions; deltaSampleSize::Union{Vector{typeof(1.0u"m")}, Nothing} = nothing) = @warn "Storing positions of type $(typeof(positions)) in MDF is not implemented" From dc202b6e6e720ae3e1d0abf15876d32c3d588a0c Mon Sep 17 00:00:00 2001 From: nHackel Date: Fri, 3 Nov 2023 17:57:26 +0100 Subject: [PATCH 067/168] In first pause let values assert their level towards the end not the start --- src/Devices/DAQ/RedPitayaDAQ.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index b5af307c..1f3084a2 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -757,7 +757,7 @@ function prepareOffsetSwitches(offsets::Vector{Vector{T}}, channels::Vector{Prot for (i, ch) in enumerate(sortedChannels) numSwitchSteps = get(switchSteps, ch, 0) pushfirst!(sortedOffsetsWithPause[i], fill(first(sortedOffsetsWithPause[i]), maxPause)...) - pushfirst!(sortedOthersPause[i], vcat(fill(false, numSwitchSteps), fill(true, maxPause - numSwitchSteps))...) + pushfirst!(sortedOthersPause[i], vcat(fill(true, maxPause - numSwitchSteps))..., fill(false, numSwitchSteps)) end return sortedOffsetsWithPause, sortedOthersPause, perm From fa4f3e1e64ebe858c0625da635062adcdf20e9d0 Mon Sep 17 00:00:00 2001 From: "mpiuser (IMT MPS)" <> Date: Tue, 21 Nov 2023 12:38:58 +0100 Subject: [PATCH 068/168] JA: fixes on IMT MPS --- src/Devices/DAQ/DAQ.jl | 12 +++++++---- src/Devices/DAQ/RedPitayaDAQ.jl | 2 +- src/Devices/Virtual/TxDAQController.jl | 9 ++++---- src/Sequences/ContinuousElectricalChannel.jl | 22 +++++++++----------- 4 files changed, 24 insertions(+), 21 deletions(-) diff --git a/src/Devices/DAQ/DAQ.jl b/src/Devices/DAQ/DAQ.jl index 9e37496d..e73058ed 100644 --- a/src/Devices/DAQ/DAQ.jl +++ b/src/Devices/DAQ/DAQ.jl @@ -119,7 +119,7 @@ function createDAQChannels(dict::Dict{String, Any}) channels[key] = createDAQChannel(DAQTxChannelParams, value) elseif value["type"] == "rx" channels[key] = createDAQChannel(DAQRxChannelParams, value) - elseif value["type"] == "tx_slow" + elseif value["type"] == "txSlow" channels[key] = createDAQChannel(RedPitayaLUTChannelParams, value) end end @@ -216,10 +216,14 @@ isWaveformAllowed(daq::AbstractDAQ, channelID::AbstractString, waveform::Wavefor feedback(daq::AbstractDAQ, channelID::AbstractString) = channel(daq, channelID).feedback feedbackChannelID(daq::AbstractDAQ, channelID::AbstractString) = feedback(daq, channelID).channelID function feedbackCalibration(daq::AbstractDAQ, channelID::AbstractString) - if isa(feedback(daq, channelID).calibration, String) # if TF has not been loaded yet, load the h5 file + if !isnothing(feedback(daq, channelID)) && isa(feedback(daq, channelID).calibration, String) # if TF has not been loaded yet, load the h5 file feedback(daq, channelID).calibration = TransferFunction(joinpath(configDir(daq),"TransferFunctions",feedback(daq, channelID).calibration)) else - return feedback(daq, channelID).calibration + if !isnothing(feedback(daq, channelID)) + return feedback(daq, channelID).calibration + else + return nothing + end end end function calibration(daq::AbstractDAQ, channelID::AbstractString) @@ -265,7 +269,7 @@ function applyForwardCalibration!(seq::Sequence, daq::AbstractDAQ) end end end - + for lutChannel in acyclicElectricalTxChannels(seq) if lutChannel isa StepwiseElectricalChannel values = lutChannel.values diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index 567ae25d..fc1679f9 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -128,7 +128,7 @@ function setRampingParams(daq::RedPitayaDAQ, sequence::Sequence) for channel in txChannels m = nothing idx = channel[2].channelIdx - if channel[2] isa TxChannelParams + if channel[2] isa DAQTxChannelParams m = idx elseif channel[2] isa RedPitayaLUTChannelParams # Map to fast DAC diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index f1a9e77a..9143c549 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -118,11 +118,11 @@ function decideControlSequenceType(target::Sequence, findZeroDC::Bool=false) needsDecoupling_ = needsDecoupling(target) @debug "decideControlSequenceType:" hasAWComponent moreThanOneComponent moreThanThreeChannels moreThanOneField needsDecoupling_ findZeroDC - if needsDecoupling_ && !hasAWComponent && !moreThanOneField && !moreThanThreeChannels && !moreThanOneComponent && findZeroDC + if needsDecoupling_ && !hasAWComponent && !moreThanOneField && !moreThanThreeChannels && !moreThanOneComponent && !findZeroDC return CrossCouplingControlSequence elseif needsDecoupling_ throw(SequenceConfigurationError("The given sequence can not be controlled! To control a field with decoupling it cannot have an AW component ($hasAWComponent), more than one field ($moreThanOneField), more than three channels ($moreThanThreeChannels) nor more than one component per channel ($moreThanOneComponent). DC control ($findZeroDC) is also not possible")) - elseif !hasAWComponent && !moreThanOneField && !moreThanThreeChannels && !moreThanOneComponent && findZeroDC + elseif !hasAWComponent && !moreThanOneField && !moreThanThreeChannels && !moreThanOneComponent && !findZeroDC return CrossCouplingControlSequence else return AWControlSequence @@ -395,7 +395,8 @@ function controlTx(txCont::TxDAQController, control::ControlSequence) # Prepare control measurement setup(daq, control.currSequence) - if "checkEachControlStep" in split(ENV["JULIA_DEBUG"],",") + + if haskey(ENV, "JULIA_DEBUG") && "checkEachControlStep" in split(ENV["JULIA_DEBUG"],",") menu = REPL.TerminalMenus.RadioMenu(["Continue", "Abort"], pagesize=2) choice = REPL.TerminalMenus.request("Please confirm the current values for control:", menu) if choice == 1 @@ -584,7 +585,7 @@ function checkFieldDeviation(cont::ControlSequence, txCont::TxDAQController, Γ: end @debug "Check field deviation [T]" Ω Γ @debug "Ω - Γ = " abs_deviation rel_deviation phase_deviation - @info "Observed field deviation:\nabs:\t$(abs_deviation*1000) mT\nrel:\t$(rel_deviation*100) %\nφ:\t$(phase_deviation/pi*180)°\n allowed: $(txCont.params.absoluteAmplitudeAccuracy|>u"mT"), $(txCont.params.relativeAmplitudeAccuracy*100) %, $(uconvert(u"°",txCont.params.phaseAccuracy))" + @info "Observed field deviation:\nabs:\t$(abs_deviation*1000) mT\nrel:\t$(rel_deviation*100) %\nphi:\t$(phase_deviation/pi*180)°\n allowed: $(txCont.params.absoluteAmplitudeAccuracy|>u"mT"), $(txCont.params.relativeAmplitudeAccuracy*100) %, $(uconvert(u"°",txCont.params.phaseAccuracy))" phase_ok = abs.(phase_deviation) .< ustrip(u"rad", txCont.params.phaseAccuracy) amplitude_ok = (abs.(abs_deviation) .< ustrip(u"T", txCont.params.absoluteAmplitudeAccuracy)) .| (abs.(rel_deviation) .< txCont.params.relativeAmplitudeAccuracy) @debug "Field deviation:" amplitude_ok phase_ok diff --git a/src/Sequences/ContinuousElectricalChannel.jl b/src/Sequences/ContinuousElectricalChannel.jl index 0ab203ed..c19415a8 100644 --- a/src/Sequences/ContinuousElectricalChannel.jl +++ b/src/Sequences/ContinuousElectricalChannel.jl @@ -51,19 +51,17 @@ function createFieldChannel(channelID::AbstractString, channelType::Type{Continu end if haskey(channelDict, "phase") - phaseDict = Dict("sine"=>0.0u"rad", "sin"=>0.0u"rad","cosine"=>pi/2u"rad", "cos"=>pi/2u"rad","-sine"=>pi*u"rad", "-sin"=>pi*u"rad","-cosine"=>-pi/2u"rad", "-cos"=>-pi/2u"rad") - phase = [] - for x in channelDict["phase"] - try - push!(phase, uparse.(x)) - catch - if haskey(phaseDict, x) - push!(phase, phaseDict[x]) - else - error("The value $x for the phase could not be parsed. Use either a unitful value, or one of the predefined keywords ($(keys(phaseDict)))") - end + phaseDict = Dict("cosine"=>0.0u"rad", "cos"=>0.0u"rad","sine"=>pi/2u"rad", "sin"=>pi/2u"rad","-cosine"=>pi*u"rad", "-cos"=>pi*u"rad","-sine"=>-pi/2u"rad", "-sin"=>-pi/2u"rad") + + try + phase = uparse(channelDict["phase"]) + catch + if haskey(phaseDict, channelDict["phase"]) + phase = phaseDict[channelDict["phase"]] + else + error("The value $(channelDict["phase"]) for the phase could not be parsed. Use either a unitful value, or one of the predefined keywords ($(keys(phaseDict)))") end - end + end else phase = 0.0u"rad" # Default phase end From 40e374810b9518e0964de1d57ab3c692b1fda5b6 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Wed, 22 Nov 2023 12:02:46 +0100 Subject: [PATCH 069/168] remove unnecessary restriction on decimation --- src/Devices/DAQ/RedPitayaDAQ.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index fc1679f9..5f3e1818 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -586,9 +586,9 @@ function setupRx(daq::RedPitayaDAQ, sequence::Sequence) @assert txBaseFrequency(sequence) == 125.0u"MHz" "The base frequency is fixed for the Red Pitaya "* "and must thus be 125 MHz and not $(txBaseFrequency(sequence))." - # The decimation can only be a power of 2 beginning with 8 + # The decimation has to be divisible by 2 and must be 8 or larger decimation_ = upreferred(txBaseFrequency(sequence)/rxSamplingRate(sequence)) - if decimation_ in [2^n for n in 3:8] + if iseven(decimation_) && decimation_ >= 8 daq.decimation = decimation_ else throw(ScannerConfigurationError("The decimation derived from the rx bandwidth of $(rxBandwidth(sequence)) and "* From 291579f75cb05238abea21adea17156d89e83377 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Wed, 22 Nov 2023 13:38:55 +0100 Subject: [PATCH 070/168] add dfSamplesPerCycle() + small fixes --- src/Devices/Virtual/TxDAQController.jl | 11 +++++------ src/Protocols/Storage/MDF.jl | 2 +- src/Sequences/Sequence.jl | 9 ++++++--- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index 9143c549..f186851d 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -145,7 +145,7 @@ function createRFFTindices(controlledChannelsDict::OrderedDict{PeriodicElectrica for (i, channel) in enumerate(keys(controlledChannelsDict)) for (j, comp) in enumerate(components(channel)) - dfCyclesPerPeriod = Int(lcm(dfDivider(seq))/divider(comp)) + dfCyclesPerPeriod = Int(dfSamplesPerCycle(seq)/divider(comp)) if isa(comp, PeriodicElectricalComponent) index_mask[i,j,dfCyclesPerPeriod+1] = true elseif isa(comp, ArbitraryElectricalComponent) @@ -188,7 +188,7 @@ function createLUTs(seqChannel::Vector{PeriodicElectricalChannel}, seq::Sequence N = rxNumSamplingPoints(seq) D = length(seqChannel) - dfCyclesPerPeriod = Int[lcm(dfDivider(seq))/divider(components(chan)[i]) for (i,chan) in enumerate(seqChannel)] + dfCyclesPerPeriod = Int[dfSamplesPerCycle(seq)/divider(components(chan)[i]) for (i,chan) in enumerate(seqChannel)] sinLUT = zeros(D,N) cosLUT = zeros(D,N) @@ -383,10 +383,10 @@ function controlTx(txCont::TxDAQController, control::ControlSequence) dc_correction = -ref_offsets_0./((ref_offsets_δ.-ref_offsets_0)./δ) + @info "Result of finding Zero DC" ref_offsets_0' ref_offsets_δ' dc_correction' if any(abs.(dc_correction).>maximum(δ)) # We do not accept any change that is larger than what we would expect for 1mT - error("DC correction too large!") + error("DC correction too large! Wanted to set $(dc_correction)") end - @debug "Result of finding Zero DC" ref_offsets_0' ref_offsets_δ' dc_correction' control.dcCorrection = dc_correction end @@ -395,7 +395,6 @@ function controlTx(txCont::TxDAQController, control::ControlSequence) # Prepare control measurement setup(daq, control.currSequence) - if haskey(ENV, "JULIA_DEBUG") && "checkEachControlStep" in split(ENV["JULIA_DEBUG"],",") menu = REPL.TerminalMenus.RadioMenu(["Continue", "Abort"], pagesize=2) choice = REPL.TerminalMenus.request("Please confirm the current values for control:", menu) @@ -876,7 +875,7 @@ function checkFieldToVolt(oldTx::Matrix{<:Complex}, Γ::Matrix{<:Complex}, cont: @debug "checkFieldToVolt: We expected $(calibFieldToVoltEstimate[mask]) V/T and got $(calibFieldToVoltMeasured[mask]) V/T, deviation: $abs_deviation" valid = maximum( abs_deviation ) < txCont.params.fieldToVoltDeviation if !valid - @warn "Measured field to volt deviates by $(abs_deviation*100) % from estimate, exceeding allowed deviation of $(txCont.params.fieldToVoltDeviation*100) %" + @error "Measured field to volt deviates by $(abs_deviation*100) % from estimate, exceeding allowed deviation of $(txCont.params.fieldToVoltDeviation*100) %" elseif maximum(abs.(phase_deviation)) > 10/180*pi @warn "The phase of the measured field to volt deviates by $phase_deviation from estimate. Please check you phases! Continuing anyways..." end diff --git a/src/Protocols/Storage/MDF.jl b/src/Protocols/Storage/MDF.jl index f1ad749d..97ee7066 100644 --- a/src/Protocols/Storage/MDF.jl +++ b/src/Protocols/Storage/MDF.jl @@ -303,7 +303,7 @@ function fillMDFAcquisition(mdf::MDFv2InMemory, scanner::MPIScanner, sequence::S numFreq = div(numSamplingPoints_,2)+1 freq = collect(0:(numFreq-1))./(numFreq-1).*ustrip(u"Hz", rxBandwidth(sequence)) tf_ = TransferFunction(scanner) - tf = tf_(freq,1:numRxChannels_) # TODO/JA: check if sampleTF can be used here! + tf = ustrip.(tf_(freq,1:numRxChannels_)) # TODO/JA: check if sampleTF can be used here! MPIFiles.rxTransferFunction(mdf, tf) MPIFiles.rxInductionFactor(mdf, tf_.inductionFactor) end diff --git a/src/Sequences/Sequence.jl b/src/Sequences/Sequence.jl index 456ef826..b6fa671f 100644 --- a/src/Sequences/Sequence.jl +++ b/src/Sequences/Sequence.jl @@ -267,7 +267,7 @@ function acqNumPeriodsPerFrame(sequence::Sequence) #TODO: We can't limit this to acyclic channels. What is the correct number of periods per frame with mechanical channels? if hasAcyclicElectricalTxChannels(sequence) channels = acyclicElectricalTxChannels(sequence) - samplesPerCycle = lcm(dfDivider(sequence)) + samplesPerCycle = dfSamplesPerCycle(sequence) numPeriods = [div(c.divider, samplesPerCycle) for c in channels ] if minimum(numPeriods) != maximum(numPeriods) @@ -285,7 +285,7 @@ function acqNumPeriodsPerPatch(sequence::Sequence) #TODO: We can't limit this to acyclic channels. What is the correct number of periods per frame with mechanical channels? if hasAcyclicElectricalTxChannels(sequence) channels = acyclicElectricalTxChannels(sequence) - samplesPerCycle = lcm(dfDivider(sequence)) + samplesPerCycle = dfSamplesPerCycle(sequence) stepsPerCycle = [ typeof(c) <: StepwiseElectricalChannel ? div(c.divider,length(c.values)*samplesPerCycle) : div(c.dividerSteps,samplesPerCycle) for c in channels ] @@ -323,8 +323,11 @@ dfBaseFrequency(sequence::Sequence) = baseFrequency(sequence) export txBaseFrequency txBaseFrequency(sequence::Sequence) = dfBaseFrequency(sequence) # Alias, since this might not only concern the drivefield +export dfSamplesPerCycle +dfSamplesPerCycle(sequence::Sequence) = lcm(dfDivider(sequence)) + export dfCycle -dfCycle(sequence::Sequence) = lcm(dfDivider(sequence))/dfBaseFrequency(sequence) |> u"s" +dfCycle(sequence::Sequence) = dfSamplesPerCycle(sequence)/dfBaseFrequency(sequence) |> u"s" export txCycle txCycle(sequence::Sequence) = dfCycle(sequence) # Alias, since this might not only concern the drivefield From d9f52004da4fd8302ed2a2cd07d82facae99d5c3 Mon Sep 17 00:00:00 2001 From: nHackel Date: Fri, 8 Dec 2023 11:59:59 +0100 Subject: [PATCH 071/168] Readd Statistics dependency --- Project.toml | 1 + src/MPIMeasurements.jl | 1 + 2 files changed, 2 insertions(+) diff --git a/Project.toml b/Project.toml index 25b5cbc6..c44ea00f 100644 --- a/Project.toml +++ b/Project.toml @@ -20,6 +20,7 @@ Reexport = "189a3867-3050-52da-a836-e630ba90ab69" ReplMaker = "b873ce64-0db9-51f5-a568-4457d8e49576" Scratch = "6c6a2e73-6563-6170-7368-637461726353" Sockets = "6462fe0b-24de-5631-8697-dd941f90decc" +Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" StringEncodings = "69024149-9ee7-55f6-a4c4-859efe599b68" TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76" ThreadPools = "b189fb0b-2eb5-4ed4-bc0c-d34c51242431" diff --git a/src/MPIMeasurements.jl b/src/MPIMeasurements.jl index d75760e8..e1d50506 100644 --- a/src/MPIMeasurements.jl +++ b/src/MPIMeasurements.jl @@ -15,6 +15,7 @@ using InteractiveUtils using Mmap using Scratch using StringEncodings +using Statistics using DocStringExtensions using MacroTools using LibSerialPort From c298d3aa7394d2ff742fe91687c88362bfca0921 Mon Sep 17 00:00:00 2001 From: nHackel Date: Wed, 20 Dec 2023 17:21:20 +0100 Subject: [PATCH 072/168] Store calibSize for hybrid mps SMs --- src/Devices/DAQ/RedPitayaDAQ.jl | 6 ++++-- src/Protocols/MPSMeasurementProtocol.jl | 8 ++++++-- src/Protocols/Storage/MDF.jl | 8 ++++++-- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index 1f3084a2..ec8fdded 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -473,10 +473,12 @@ function prepareProtocolSequences(base::Sequence, daq::RedPitayaDAQ; numPeriodsP # Prepare offset sequences offsetVector = ProtocolOffsetElectricalChannel[] fieldMap = Dict{ProtocolOffsetElectricalChannel, MagneticField}() + calibsize = Int64[] for field in cpy for channel in channels(field, ProtocolOffsetElectricalChannel) push!(offsetVector, channel) fieldMap[channel] = field + push!(calibsize, length(values(channel))) end end @@ -520,7 +522,7 @@ function prepareProtocolSequences(base::Sequence, daq::RedPitayaDAQ; numPeriodsP end end - return cpy, permutation, offsets + return cpy, permutation, offsets, calibsize end function prepareHSwitchedOffsets(offsetVector::Vector{ProtocolOffsetElectricalChannel}, daq::RedPitayaDAQ, seq::Sequence, stepduration) @@ -757,7 +759,7 @@ function prepareOffsetSwitches(offsets::Vector{Vector{T}}, channels::Vector{Prot for (i, ch) in enumerate(sortedChannels) numSwitchSteps = get(switchSteps, ch, 0) pushfirst!(sortedOffsetsWithPause[i], fill(first(sortedOffsetsWithPause[i]), maxPause)...) - pushfirst!(sortedOthersPause[i], vcat(fill(true, maxPause - numSwitchSteps))..., fill(false, numSwitchSteps)) + pushfirst!(sortedOthersPause[i], vcat(fill(true, maxPause - numSwitchSteps))..., fill(false, numSwitchSteps)...) end return sortedOffsetsWithPause, sortedOthersPause, perm diff --git a/src/Protocols/MPSMeasurementProtocol.jl b/src/Protocols/MPSMeasurementProtocol.jl index 1d265781..c569ab9b 100644 --- a/src/Protocols/MPSMeasurementProtocol.jl +++ b/src/Protocols/MPSMeasurementProtocol.jl @@ -63,6 +63,7 @@ Base.@kwdef mutable struct MPSMeasurementProtocol <: Protocol sequence::Union{Sequence, Nothing} = nothing offsetfields::Union{Matrix{Float64}, Nothing} = nothing patchPermutation::Vector{Int64} = Int64[] + calibsize::Vector{Int64} = Int64[] bgMeas::Array{Float32, 4} = zeros(Float32,0,0,0,0) done::Bool = false @@ -97,10 +98,11 @@ function _init(protocol::MPSMeasurementProtocol) protocol.protocolMeasState = ProtocolMeasState() try - seq, perm, offsets = prepareProtocolSequences(protocol.params.sequence, getDAQ(scanner(protocol)); numPeriodsPerPatch = protocol.params.dfPeriodsPerOffset) + seq, perm, offsets, calibsize = prepareProtocolSequences(protocol.params.sequence, getDAQ(scanner(protocol)); numPeriodsPerPatch = protocol.params.dfPeriodsPerOffset) protocol.sequence = seq protocol.patchPermutation = perm protocol.offsetfields = ustrip.(u"T", offsets) # TODO make robust + protocol.calibsize = calibsize catch e throw(e) end @@ -322,7 +324,9 @@ function handleEvent(protocol::MPSMeasurementProtocol, event::DatasetStoreStorag if protocol.params.saveAsSystemMatrix isBGFrame = repeat(isBGFrame, inner = div(size(data, 3), protocol.params.dfPeriodsPerOffset)) data = reshape(data, size(data, 1), size(data, 2), protocol.params.dfPeriodsPerOffset, :) - offsets = reshape(offsets, protocol.params.dfPeriodsPerOffset, :, size(offsets, 2))[1, :, :] # All periods in one frame (should) have same offset + # All periods in one frame (should) have same offset + offsets = reshape(offsets, protocol.params.dfPeriodsPerOffset, :, size(offsets, 2))[1, :, :] + offsets = reshape(offsets, protocol.calibsize..., :) # make calib size "visible" to storing function filename = saveasMDF(store, scanner, protocol.sequence, data, offsets, isBGFrame, mdf, storeAsSystemMatrix=protocol.params.saveAsSystemMatrix, drivefield = drivefield, temperatures = temperature, applied = appliedField) else filename = saveasMDF(store, scanner, protocol.sequence, data, isBGFrame, mdf, drivefield = drivefield, temperatures = temperature, applied = appliedField) diff --git a/src/Protocols/Storage/MDF.jl b/src/Protocols/Storage/MDF.jl index 4e0113da..c7201acc 100644 --- a/src/Protocols/Storage/MDF.jl +++ b/src/Protocols/Storage/MDF.jl @@ -58,7 +58,7 @@ end function MPIFiles.saveasMDF(store::DatasetStore, scanner::MPIScanner, sequence::Sequence, data::Array{Float32,4}, - positions::Union{Positions, Matrix}, isBackgroundFrame::Vector{Bool}, mdf::MDFv2InMemory; storeAsSystemMatrix::Bool = false, deltaSampleSize::Union{Vector{typeof(1.0u"m")}, Nothing} = nothing, temperatures::Union{Array{Float32}, Nothing}=nothing, drivefield::Union{Array{ComplexF64}, Nothing}=nothing, applied::Union{Array{ComplexF64}, Nothing}=nothing) + positions::Union{Positions, AbstractArray}, isBackgroundFrame::Vector{Bool}, mdf::MDFv2InMemory; storeAsSystemMatrix::Bool = false, deltaSampleSize::Union{Vector{typeof(1.0u"m")}, Nothing} = nothing, temperatures::Union{Array{Float32}, Nothing}=nothing, drivefield::Union{Array{ComplexF64}, Nothing}=nothing, applied::Union{Array{ComplexF64}, Nothing}=nothing) if storeAsSystemMatrix study = MPIFiles.getCalibStudy(store) @@ -135,7 +135,7 @@ function fillMDFCalibration(mdf::MDFv2InMemory, positions::GridPositions; deltaS return end -function fillMDFCalibration(mdf::MDFv2InMemory, offsetFields::Union{Matrix, Nothing}; deltaSampleSize::Union{Vector{typeof(1.0u"m")}, Nothing} = nothing) +function fillMDFCalibration(mdf::MDFv2InMemory, offsetFields::Union{AbstractArray, Nothing}; deltaSampleSize::Union{Vector{typeof(1.0u"m")}, Nothing} = nothing) # /calibration/ subgroup @@ -146,11 +146,15 @@ function fillMDFCalibration(mdf::MDFv2InMemory, offsetFields::Union{Matrix, Noth method = "hybrid" order = "xyz" + calibsize = size(offsetFields)[1:end-1] + offsetFields = reshape(offsetFields, prod(calibsize), :) + mdf.calibration = MDFv2Calibration(; deltaSampleSize = deltaSampleSize, method = method, offsetFields = offsetFields, order = order, + size = collect(calibsize) ) return From a29427fb1209a12ab15df1572e865906eb08a52e Mon Sep 17 00:00:00 2001 From: nHackel Date: Fri, 19 Jan 2024 17:43:50 +0100 Subject: [PATCH 073/168] Fix bugs in level switching and dead patches logic --- src/Devices/DAQ/RedPitayaDAQ.jl | 165 ++++++++++++++++++++------------ 1 file changed, 104 insertions(+), 61 deletions(-) diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index ec8fdded..e7c4ca47 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -500,6 +500,9 @@ function prepareProtocolSequences(base::Sequence, daq::RedPitayaDAQ; numPeriodsP # Increasing the divider instead of repeating values allows the user to tune offset length s.t. the RP can keep up divider = df * numOffsets * numPeriodsPerPatch + # offsets and permutation refer to offsets without multiple df per period + # As the signals are held longer, we have to extend each offset and permutation for numPeriodsPerPatch + # s.t. the correct patches can be cut out later if numPeriodsPerPatch > 1 updatedPermutation = Int64[] for patch in permutation @@ -531,9 +534,11 @@ function prepareHSwitchedOffsets(offsetVector::Vector{ProtocolOffsetElectricalCh regularChannel = isempty(switchingChannel) ? offsetVector : offsetVector[filter(!in(switchingIndices), 1:length(offsetVector))] allSteps = Dict{ProtocolOffsetElectricalChannel, Vector{Any}}() allEnables = Dict{ProtocolOffsetElectricalChannel, Vector{Union{Bool, Missing}}}() + validSteps = Union{Bool, Missing}[] # Prepare values with missing for h-bridge switch # Permute patches by considering quadrants. Use lower bits of number to encode permutations + # for example 011 means channel 1 is disabled, channel 2 and 3 h-bridge is enabled for comb in 0:2^length(switchingChannel)-1 quadrant = map(Bool, digits(comb, base = 2, pad=length(switchingChannel))) @@ -548,15 +553,17 @@ function prepareHSwitchedOffsets(offsetVector::Vector{ProtocolOffsetElectricalCh end orderedChannel = collect(Iterators.flatten((regularChannel, switchingChannel))) - quadrantSteps, othersPause, perm = prepareOffsetSwitches(offsets, orderedChannel, daq, stepduration) + + # For each quadrant we can generate the offsets individually + quadrantSteps, couldPause, anySwitching, perm = prepareOffsetSwitches(offsets, orderedChannel, daq, stepduration) for (i, ch) in enumerate(orderedChannel[perm]) steps = get(allSteps, ch, []) enables = get(allEnables, ch, Union{Bool, Missing}[]) push!(steps, quadrantSteps[i]...) - push!(enables, map(x-> channel(daq, id(ch)).switchEnable ? true : !x, othersPause[i])...) + push!(enables, map(x-> channel(daq, id(ch)).switchEnable ? true : !x, couldPause[i])...) - # If not at the end then add missing + # If not at the end then add missing, here H-bridge switches will be inserted later if comb != 2^length(switchingChannel)-1 push!(steps, missing) push!(enables, missing) @@ -565,6 +572,12 @@ function prepareHSwitchedOffsets(offsetVector::Vector{ProtocolOffsetElectricalCh allSteps[ch] = steps allEnables[ch] = enables end + + # Steps are valid if no channel has a switch pause + push!(validSteps, map(!, anySwitching)...) + if comb != 2^length(switchingChannel)-1 + push!(validSteps, missing) + end end hbridges = prepareHBridgeLevels(allSteps, daq) @@ -581,6 +594,11 @@ function prepareHSwitchedOffsets(offsetVector::Vector{ProtocolOffsetElectricalCh allEnables[ch] = temp end + # Likewise set valid to false during hbridge switching + tempValid = Bool[] + foreach(x-> ismissing(x) ? push!(tempValid, fill(false, numSwitchPeriods)...) : push!(tempValid, x), validSteps) + validSteps = tempValid + # Prepare H-Bridge pauses for (ch, steps) in allSteps temp = [] @@ -603,7 +621,6 @@ function prepareHSwitchedOffsets(offsetVector::Vector{ProtocolOffsetElectricalCh end end - # Construct permutation mask # Generate offsets without permutation offsets = map(values, offsetVector) offsets = hcat(prepareOffsets(offsets, daq)...) @@ -613,14 +630,12 @@ function prepareHSwitchedOffsets(offsetVector::Vector{ProtocolOffsetElectricalCh offsetDict[row] = i end - # Permutation Mask - enableMatrix = hcat(values(allEnables)...) - permutationMask = map(all, eachrow(enableMatrix)) + # Permute offsets # Sort patches according to their value in the index dictionary permoffsets = hcat(map(x-> allSteps[x], offsetVector)...) - patchPermutation = sortperm(map(pair -> permutationMask[pair[1]] ? offsetDict[identity(pair[2])] : typemax(Int64), enumerate(eachrow(permoffsets)))) + patchPermutation = sortperm(map(pair -> validSteps[pair[1]] ? offsetDict[identity(pair[2])] : typemax(Int64), enumerate(eachrow(permoffsets)))) # Pause/switching patches are sorted to the end and need to be removed - patchPermutation = patchPermutation[1:end-length(filter(!identity, permutationMask))] + patchPermutation = patchPermutation[1:end-length(filter(!identity, validSteps))] # Remove (negative) sign from all channels with an h-bridge for (ch, steps) in filter(x->haskey(hbridges, x[1]), allSteps) @@ -635,13 +650,13 @@ function prepareOffsets(offsetVector::Vector{ProtocolOffsetElectricalChannel}, d enables = Dict{ProtocolOffsetElectricalChannel, Vector{Bool}}() offsets = map(values, offsetVector) - steps, othersPause, perm = prepareOffsetSwitches(offsets, offsetVector, daq, stepduration) + steps, couldPause, anySwitching, perm = prepareOffsetSwitches(offsets, offsetVector, daq, stepduration) + validSteps = map(!, anySwitching) for (i, ch) in enumerate(offsetVector[perm]) allSteps[ch] = steps[i] - enables[ch] = map(x-> channel(daq, id(ch)).switchEnable ? true : !x, othersPause[i]) + enables[ch] = map(x-> channel(daq, id(ch)).switchEnable ? true : !x, couldPause[i]) end - # Construct permutation mask # Map each offset combination to its original index offsets = hcat(prepareOffsets(offsets, daq)...) offsetDict = Dict{Vector, Int64}() @@ -649,14 +664,12 @@ function prepareOffsets(offsetVector::Vector{ProtocolOffsetElectricalChannel}, d offsetDict[row] = i end - # Permutation Mask - enableMatrix = hcat(values(enables)...) - permutationMask = map(all, eachrow(enableMatrix)) + # Permute offsets # Sort patches according to their value in the index dictionary permoffsets = hcat(map(x-> allSteps[x], offsetVector)...) - patchPermutation = sortperm(map(pair -> permutationMask[pair[1]] ? offsetDict[identity(pair[2])] : typemax(Int64), enumerate(eachrow(permoffsets)))) + patchPermutation = sortperm(map(pair -> validSteps[pair[1]] ? offsetDict[identity(pair[2])] : typemax(Int64), enumerate(eachrow(permoffsets)))) # Switching patches are sorted to the end and need to be removed - patchPermutation = patchPermutation[1:end-length(filter(!identity, permutationMask))] + patchPermutation = patchPermutation[1:end-length(filter(!identity, validSteps))] hbridges = prepareHBridgeLevels(allSteps, daq) # Remove (negative) sign from all channels with an h-bridge @@ -706,63 +719,93 @@ function prepareOffsetSwitches(offsets::Vector{Vector{T}}, channels::Vector{Prot if isempty(switchSteps) offsets = prepareOffsets(offsets, daq) - return offsets, [fill(true, length(first(offsets))) for ch in channels], 1:length(channels) + return offsets, [fill(false, length(first(offsets))) for ch in channels], fill(false, length(first(offsets))), 1:length(channels) end # Sorting is not optimal, but allows us in the next step to consider pauses individually and not have to take max of all possible pauses at a point perm = sortperm(map(x-> haskey(switchSteps, x) ? switchSteps[x] : 0, channels)) + + # The idea is to realize the following pattern for n-channel (shown with three: x, y, z) + # A switch S_x means the next "proper" offset value is to be repeated S_x times, the amount of switch steps x requires + # x_i means the i'th value of the x offsets + # ()_i means loop the pattern in the bracket i times + + # i = number of x offsets, j = # y offsets , k = # z offsets + # x: (S_z - S_y (S_y - S_ x (S_x, x_i)_i)_j)_k + # x: (S_z - S_y (S_y - S_ x (S_x, y_j)_i)_j)_k + # x: (S_z - S_y (S_y - S_ x (S_x, z_k)_i)_j)_k + sortedOffsets = offsets[perm] sortedChannels = channels[perm] + sortedSwitchSteps = map(ch -> get(switchSteps, ch, 0), sortedChannels) + + sortedOffsetsWithSwitches = [] + sortedCouldPause = [] + anyChannelSwitching = Bool[] + + # Each channel will add the same switch "pauses" + # This array contains the precomputed S_x, S_y - S_x, ..., + sortedLoopSteps = zeros(Int64, size(sortedSwitchSteps)) + previous = 0 + for (i, pause) in enumerate(sortedSwitchSteps) + sortedLoopSteps[i] = pause - previous + previous = pause + end + + # The pattern will be constructed with a "nested" for loop. + # However, the nesting depth depends on the number of channels. To make this generic to n-channel we will use CartesianIndices + iterations = map(length, sortedOffsets) + loop = CartesianIndices(Tuple(iterations)) + + # Here we want to have a loop as follows (but generic for n-channels): + # for ch in channels + # for k = 1:length(z) + # for j = 1:length(y) + # for i = 1:length(x) + # # ... create pattern + for (channelIdx, channel) in enumerate(sortedChannels) + offsetVec = sortedOffsets[channelIdx] + resultOffsets = eltype(offsetVec)[] # Store the offsets of the current channel + resultCouldPause = Bool[] # Store when the current channel doesn't need to send (i.e others switch and he doesn't yet) + resultAnySwitching = Bool[] # Store when any channel is switching + + previous = CartesianIndex(Tuple(fill(-1, length(iterations)))) # This will be used to detect which index is currently "running"/changing + for indices in eachindex(loop) + value = offsetVec[indices[channelIdx]] + + # Apply (loop) switch steps for each changing index, i.e. an index that is not equal to its previous iteration value + requiredSwitches = map(!, iszero.(Tuple(indices - previous))) + + # Compute how many switching "pauses" are to be inserted + switches = 0 + switchIndices = findall(requiredSwitches) + if !isempty(switchIndices) + switches = sum(sortedLoopSteps[findall(requiredSwitches)]) + end + # If the current channel is switching, we need to subtract that from couldPause + ownSwitches = in(channelIdx, switchIndices) ? sortedSwitchSteps[channelIdx] : 0 + + # + 1 is for the actual offset + push!(resultOffsets, fill(value, 1 + switches)...) + push!(resultCouldPause, vcat(fill(true, switches - ownSwitches), fill(false, 1 + ownSwitches))...) - sortedOffsetsWithPause = [] - sortedOthersPause = [] - for (i, ch) in enumerate(sortedChannels) - inner = isempty(sortedOffsetsWithPause) ? 1 : length(last(sortedOffsetsWithPause)) - offs = [repeat([value], inner = inner) for value in sortedOffsets[i]] - othersPause = [push!(repeat([true], inner = inner - 1), false) for value in sortedOffsets[i]] - - numSwitchSteps = get(switchSteps, ch, 0) - # Consider own pauses - if !iszero(numSwitchSteps) - for (k, level) in enumerate(offs[2:end]) - push!(level, fill(first(level), numSwitchSteps)...) - push!(othersPause[k + 1], fill(false, numSwitchSteps)...) + # anySwitching only needs to be computed once + if isempty(anyChannelSwitching) + push!(resultAnySwitching, push!(fill(true, switches), false)...) end + + previous = indices end - offs = vcat(offs...) - othersPause = vcat(othersPause...) - - # Update other channels with own repetition number - for (j, otherOffs) in enumerate(sortedOffsetsWithPause) - # Add one pause - tempOffs = deepcopy(otherOffs) - tempOthersPause = deepcopy(sortedOthersPause[j]) - if !iszero(numSwitchSteps) - push!(tempOffs, fill(first(sortedOffsets[j]), numSwitchSteps)...) - push!(tempOthersPause, fill(true, numSwitchSteps)...) - end - # Repeat n-1 times - tempOffs = repeat(tempOffs, outer = length(sortedOffsets[i]) - 1) - tempOthersPause = repeat(tempOthersPause, outer = length(sortedOffsets[i]) - 1) - # Add last repetition without a following pause - sortedOffsetsWithPause[j] = vcat(tempOffs, otherOffs) - sortedOthersPause[j] = vcat(tempOthersPause, sortedOthersPause[j]) + + push!(sortedOffsetsWithSwitches, resultOffsets) + push!(sortedCouldPause, resultCouldPause) + if isempty(anyChannelSwitching) + anyChannelSwitching = resultAnySwitching end - - push!(sortedOthersPause, othersPause) - push!(sortedOffsetsWithPause, offs) - end - - # Add first pause - maxPause = maximum(values(switchSteps)) - for (i, ch) in enumerate(sortedChannels) - numSwitchSteps = get(switchSteps, ch, 0) - pushfirst!(sortedOffsetsWithPause[i], fill(first(sortedOffsetsWithPause[i]), maxPause)...) - pushfirst!(sortedOthersPause[i], vcat(fill(true, maxPause - numSwitchSteps))..., fill(false, numSwitchSteps)...) end - return sortedOffsetsWithPause, sortedOthersPause, perm + return sortedOffsetsWithSwitches, sortedCouldPause, anyChannelSwitching, perm end function prepareOffsets(offsetVectors::Vector{Vector{T}}, daq::RedPitayaDAQ) where T From 4534a3bcabd266d5930f00923fe9a9c3cd943890 Mon Sep 17 00:00:00 2001 From: nHackel Date: Fri, 19 Jan 2024 17:44:09 +0100 Subject: [PATCH 074/168] Remove delete df periods from mps protocol --- src/Protocols/MPSMeasurementProtocol.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Protocols/MPSMeasurementProtocol.jl b/src/Protocols/MPSMeasurementProtocol.jl index c569ab9b..0f13b2d1 100644 --- a/src/Protocols/MPSMeasurementProtocol.jl +++ b/src/Protocols/MPSMeasurementProtocol.jl @@ -27,8 +27,6 @@ Base.@kwdef mutable struct MPSMeasurementProtocolParams <: ProtocolParams # TODO: This is only for 1D MPS systems for now "Number of periods per offset of the MPS offset measurement. Overwrites parts of the sequence definition." dfPeriodsPerOffset::Integer = 2 - "Number of periods per offset which should be deleted. Acquired total number of periods is `dfPeriodsPerOffset + deletedDfPeriodsPerOffset`. Overwrites parts of the sequence definition." - deletedDfPeriodsPerOffset::Integer = 1 end function MPSMeasurementProtocolParams(dict::Dict, scanner::MPIScanner) sequence_ = nothing From ce8c7ae526031c4b93bce072659fda2a64726c0b Mon Sep 17 00:00:00 2001 From: nHackel Date: Fri, 2 Feb 2024 15:54:58 +0100 Subject: [PATCH 075/168] Allow h-bridge channel comprised of multiple signals --- src/Devices/DAQ/DAQ.jl | 17 ++++++------ src/Devices/DAQ/RedPitayaDAQ.jl | 46 ++++++++++++++++----------------- 2 files changed, 31 insertions(+), 32 deletions(-) diff --git a/src/Devices/DAQ/DAQ.jl b/src/Devices/DAQ/DAQ.jl index 3f1b12f0..5a1fd99b 100644 --- a/src/Devices/DAQ/DAQ.jl +++ b/src/Devices/DAQ/DAQ.jl @@ -62,14 +62,14 @@ Base.@kwdef struct DAQFeedback calibration::Union{typeof(1.0u"T/V"), Nothing} = nothing end -Base.@kwdef struct DAQHBridge - channelID::AbstractString +Base.@kwdef struct DAQHBridge{N} + channelID::Union{String, Vector{String}} manual::Bool = false deadTime::typeof(1.0u"s") = 0.0u"s" - level::Vector{typeof(1.0u"V")} # Can this be simplified by a convention for h-bridges? + level::Matrix{typeof(1.0u"V")} # Can this be simplified by a convention for h-bridges? end -negativeLevel(bridge::DAQHBridge) = bridge.level[1] -positiveLevel(bridge::DAQHBridge) = bridge.level[2] +negativeLevel(bridge::DAQHBridge) = bridge.level[:, 1] +positiveLevel(bridge::DAQHBridge) = bridge.level[:, 2] level(bridge::DAQHBridge, x::Number) = signbit(x) ? negativeLevel(bridge) : positiveLevel(bridge) manual(bridge::DAQHBridge) = bridge.manual deadTime(bridge::DAQHBridge) = bridge.deadTime @@ -77,8 +77,9 @@ id(bridge::DAQHBridge) = bridge.channelID function createDAQChannels(::Type{DAQHBridge}, dict::Dict{String, Any}) splattingDict = Dict{Symbol, Any}() - splattingDict[:channelID] = dict["channelID"] - splattingDict[:level] = uparse.(dict["level"]) + splattingDict[:channelID] = dict["channelID"] isa Vector ? dict["channelID"] : [dict["channelID"]] + N = length(splattingDict[:channelID]) + splattingDict[:level] = reshape(uparse.(dict["level"]), N, :) if haskey(dict, "manual") splattingDict[:manual] = dict["manual"] @@ -87,7 +88,7 @@ function createDAQChannels(::Type{DAQHBridge}, dict::Dict{String, Any}) if haskey(dict, "deadTime") splattingDict[:deadTime] = uparse(dict["deadTime"]) end - return DAQHBridge(;splattingDict...) + return DAQHBridge{N}(;splattingDict...) end Base.@kwdef struct DAQTxChannelParams <: TxChannelParams diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index e7c4ca47..c9cacd5c 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -517,11 +517,17 @@ function prepareProtocolSequences(base::Sequence, daq::RedPitayaDAQ; numPeriodsP for (ch, steps) in allSteps stepwise = StepwiseElectricalChannel(id = id(ch), divider = divider, values = identity.(steps), enable = enables[ch]) fieldMap[ch][id(stepwise)] = stepwise + + # Generate H-Bridge channels if haskey(hbridges, ch) offsetChannel = channel(daq, id(ch)) - hbridgeChannel = offsetChannel.hbridge - hbridgeStepwise = StepwiseElectricalChannel(id = id(hbridgeChannel), divider = divider, values = identity.(hbridges[ch]), enable = fill(true, length(steps))) - fieldMap[ch][id(hbridgeChannel)] = hbridgeStepwise + hbridgeChannels = id(offsetChannel.hbridge) + hbridgeValues = hbridges[ch] + for (i, hbridgeChannel) in enumerate(hbridgeChannels) + hsteps = map(x -> x[i], hbridgeValues) + hbridgeStepwise = StepwiseElectricalChannel(id = hbridgeChannel, divider = divider, values = hsteps, enable = fill(true, length(steps))) + fieldMap[ch][hbridgeChannel] = hbridgeStepwise + end end end @@ -580,13 +586,13 @@ function prepareHSwitchedOffsets(offsetVector::Vector{ProtocolOffsetElectricalCh end end - hbridges = prepareHBridgeLevels(allSteps, daq) - # Compute switch timing, assumption step is held for df * numPeriodsPerPatch deadTimes = map(x-> x.hbridge.deadTime, filter(x-> x.range == HBRIDGE, map(x->channel(daq, id(x)), offsetVector))) maxTime = maximum(map(ustrip, deadTimes)) numSwitchPeriods = Int64(ceil(maxTime/stepduration)) + hbridges = prepareHBridgeLevels(allSteps, daq, numSwitchPeriods) + # Set enable to false during hbridge switching for (ch, enables) in allEnables temp = Bool[] @@ -605,20 +611,6 @@ function prepareHSwitchedOffsets(offsetVector::Vector{ProtocolOffsetElectricalCh # Add numSwitchPeriods 0 for every missing value foreach(x-> ismissing(x) ? push!(temp, fill(zero(eltype(steps[1])), numSwitchPeriods)...) : push!(temp, x), steps) allSteps[ch] = temp - - # Add next hbridge level for each missing value - if haskey(hbridges, ch) - temp = [] - for (i, x) in enumerate(hbridges[ch]) # Assumption no H-Bridge in last value - if ismissing(x) - # For the switch take the level for the value that comes after missing - push!(temp, fill(level(channel(daq, id(ch)).hbridge, hbridges[ch][i + 1]), numSwitchPeriods)...) - else - push!(temp, x) - end - end - hbridges[ch] = temp - end end # Generate offsets without permutation @@ -680,14 +672,20 @@ function prepareOffsets(offsetVector::Vector{ProtocolOffsetElectricalChannel}, d return permoffsets, allSteps, enables, hbridges, patchPermutation end -function prepareHBridgeLevels(allSteps, daq::RedPitayaDAQ) +function prepareHBridgeLevels(allSteps, daq::RedPitayaDAQ, switchSteps::Int64 = 0) hbridges = Dict{ProtocolOffsetElectricalChannel, Vector{Any}}() - for (ch, steps) in allSteps + for ch in filter(ch -> channel(daq, id(ch)).range == HBRIDGE , keys(allSteps)) offsetChannel = channel(daq, id(ch)) - if offsetChannel.range == HBRIDGE - hsteps = map(x-> ismissing(x) ? missing : level(offsetChannel.hbridge, x), steps) - hbridges[ch] = hsteps + steps = allSteps[ch] + hsteps = [] + for (i, step) in enumerate(steps) + if ismissing(step) # Assumes only one missing per switch and no missing at last index + push!(hsteps, fill(level(offsetChannel.hbridge, steps[i + 1]), switchSteps)...) + else + push!(hsteps, level(offsetChannel.hbridge, step)) + end end + hbridges[ch] = hsteps end return hbridges end From ed8c0c38ef7782d3d7d926cf38db6db8dc5f2046 Mon Sep 17 00:00:00 2001 From: nHackel Date: Fri, 9 Feb 2024 13:07:52 +0100 Subject: [PATCH 076/168] Make offset channel mutable to allow for updates from GUI --- src/Sequences/ProtocolOffsetElectricalChannel.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Sequences/ProtocolOffsetElectricalChannel.jl b/src/Sequences/ProtocolOffsetElectricalChannel.jl index ece1f00f..09ecba0c 100644 --- a/src/Sequences/ProtocolOffsetElectricalChannel.jl +++ b/src/Sequences/ProtocolOffsetElectricalChannel.jl @@ -1,6 +1,6 @@ export ProtocolOffsetElectricalChannel -Base.@kwdef struct ProtocolOffsetElectricalChannel{T<:Union{typeof(1.0u"T"),typeof(1.0u"A"),typeof(1.0u"V")}} <: ProtocolTxChannel +Base.@kwdef mutable struct ProtocolOffsetElectricalChannel{T<:Union{typeof(1.0u"T"),typeof(1.0u"A"),typeof(1.0u"V")}} <: ProtocolTxChannel "ID corresponding to the channel configured in the scanner." id::AbstractString offsetStart::T From 787d155f6ca79e5c604d792eb476628f5a927997 Mon Sep 17 00:00:00 2001 From: nHackel Date: Fri, 9 Feb 2024 18:05:44 +0100 Subject: [PATCH 077/168] Allow for mmap files based on tuples/vecs without dense arrays --- src/Utils/MmapFiles.jl | 40 +++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/src/Utils/MmapFiles.jl b/src/Utils/MmapFiles.jl index da83d9b3..2c02be8d 100644 --- a/src/Utils/MmapFiles.jl +++ b/src/Utils/MmapFiles.jl @@ -3,23 +3,33 @@ dir(protocol::Protocol) = joinpath(dir(scanner(protocol)), name(protocol)) file(protocol::Protocol, file::String) = joinpath(mkpath(dir(protocol)), file) isfile(protocol::Protocol, name::String) = isfile(file(protocol, name)) -function mmap!(protocol::Protocol, f::String, array::Array{T,N}) where {T, N} - open(file(protocol, f), "w+") do io - write(io, N) - for s in size(array) - write(io, s) - end - write(io, array) +function mmap!(protocol::Protocol, f::String, array::Array{T,N}) where {T,N} + open(file(protocol, f), "w+") do io + write(io, N) + for s in size(array) + write(io, s) end - return mmap(protocol, f, T) + write(io, array) + end + return mmap(protocol, f, T) end -function mmap(protocol::Protocol, f::String, eltype::Type{T}) where {T} - io = open(file(protocol, f), "r+") - N = Base.read(io, Int64) - dims = [] - for i = 1:N - push!(dims, Base.read(io, Int64)) +function mmap!(protocol::Protocolm, f::String, eltype::DataType, size::Union{NTuple{N, Int64}, Vector{Int64}}) + open(file(protocol, f), "w+") do io + write(io, length(size)) + for s in size + write(io, s) end - return Mmap.mmap(io, Array{eltype,N}, tuple(dims...)) + end + return mmap(protocol, f, eltype) +end + +function mmap(protocol::Protocol, f::String, eltype::Type{T}) where {T} + io = open(file(protocol, f), "r+") + N = Base.read(io, Int64) + dims = [] + for i = 1:N + push!(dims, Base.read(io, Int64)) + end + return Mmap.mmap(io, Array{eltype,N}, tuple(dims...)) end \ No newline at end of file From 2b1a6ee9105b5bfb42e51418f27a75e4932933a8 Mon Sep 17 00:00:00 2001 From: nHackel Date: Fri, 9 Feb 2024 18:06:07 +0100 Subject: [PATCH 078/168] Add Mmap frame buffer --- src/Protocols/Storage/ChainableBuffer.jl | 30 ++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/Protocols/Storage/ChainableBuffer.jl b/src/Protocols/Storage/ChainableBuffer.jl index 9a671756..5763c351 100644 --- a/src/Protocols/Storage/ChainableBuffer.jl +++ b/src/Protocols/Storage/ChainableBuffer.jl @@ -75,6 +75,36 @@ end read(buffer::SimpleFrameBuffer) = buffer.data index(buffer::SimpleFrameBuffer) = buffer.nextFrame +mutable struct MmapFrameBuffer{A::AbstractArray{T, 4}} <: MeasurementBuffer + nextFrame::Int64 + data::A +end +function MmapFrameBuffer(protocol::Protocol, file::String, sequence::Sequence) + numFrames = acqNumFrames(sequence) + rxNumSamplingPoints = rxNumSamplesPerPeriod(sequence) + numPeriods = acqNumPeriodsPerFrame(sequence) + numChannel = length(rxChannels(sequence)) + return MmapFrameBuffer(protocol, file, Float32, (rxNumSamplingPoints, numChannel, numPeriods, numFrames)) +end +function MmapFrameBuffer(protocol::Protocol, file::String, args...) + mapped = mmap!(protocol, file, args...) + return MmapFrameBuffer(1, mapped) +end +function insert!(buffer::MmapFrameBuffer, from::Integer, frames::Array{Float32,4}) + to = from + size(frames, 4) - 1 + buffer.data[:, :, :, from:to] = frames + Mmap.sync!(buffer.data) + return to +end +function push!(buffer::MmapFrameBuffer, frames::Array{Float32,4}) + from = buffer.nextFrame + to = insert!(buffer, from, frames) + buffer.nextFrame = to + 1 + return (start = from, stop = to) +end +read(buffer::MmapFrameBuffer) = buffer.data +index(buffer::MmapFrameBuffer) = buffer.nextFrame + abstract type FieldBuffer <: SequenceBuffer end mutable struct DriveFieldBuffer{A <: AbstractArray{ComplexF64, 4}} <: FieldBuffer nextFrame::Integer From ba446f3dce01d47f2724ce83a79edbd0a1d02548 Mon Sep 17 00:00:00 2001 From: nHackel Date: Thu, 15 Feb 2024 10:56:50 +0100 Subject: [PATCH 079/168] Fix precomile issues with mmap --- src/Protocols/Storage/ChainableBuffer.jl | 2 +- src/Utils/MmapFiles.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Protocols/Storage/ChainableBuffer.jl b/src/Protocols/Storage/ChainableBuffer.jl index 5763c351..22f15af2 100644 --- a/src/Protocols/Storage/ChainableBuffer.jl +++ b/src/Protocols/Storage/ChainableBuffer.jl @@ -75,7 +75,7 @@ end read(buffer::SimpleFrameBuffer) = buffer.data index(buffer::SimpleFrameBuffer) = buffer.nextFrame -mutable struct MmapFrameBuffer{A::AbstractArray{T, 4}} <: MeasurementBuffer +mutable struct MmapFrameBuffer{T, A<:AbstractArray{T, 4}} <: MeasurementBuffer nextFrame::Int64 data::A end diff --git a/src/Utils/MmapFiles.jl b/src/Utils/MmapFiles.jl index 2c02be8d..a1730686 100644 --- a/src/Utils/MmapFiles.jl +++ b/src/Utils/MmapFiles.jl @@ -14,7 +14,7 @@ function mmap!(protocol::Protocol, f::String, array::Array{T,N}) where {T,N} return mmap(protocol, f, T) end -function mmap!(protocol::Protocolm, f::String, eltype::DataType, size::Union{NTuple{N, Int64}, Vector{Int64}}) +function mmap!(protocol::Protocol, f::String, eltype::DataType, size::Union{NTuple{N, Int64}, Vector{Int64}}) where N open(file(protocol, f), "w+") do io write(io, length(size)) for s in size From e3f2a43600f3696e0ef3ce251fae8a34fed8ca50 Mon Sep 17 00:00:00 2001 From: nHackel Date: Thu, 15 Feb 2024 15:04:09 +0100 Subject: [PATCH 080/168] Interpret MPS protocol periods as frames during acquisition --- src/Protocols/MPSMeasurementProtocol.jl | 61 ++++++++++++++++++++----- 1 file changed, 50 insertions(+), 11 deletions(-) diff --git a/src/Protocols/MPSMeasurementProtocol.jl b/src/Protocols/MPSMeasurementProtocol.jl index 0f13b2d1..0856b058 100644 --- a/src/Protocols/MPSMeasurementProtocol.jl +++ b/src/Protocols/MPSMeasurementProtocol.jl @@ -221,22 +221,56 @@ function asyncMeasurement(protocol::MPSMeasurementProtocol) sequence = protocol.sequence daq = getDAQ(scanner_) deviceBuffer = DeviceBuffer[] - if protocol.params.controlTx - sequence = controlTx(protocol.txCont, sequence) - push!(deviceBuffer, TxDAQControllerBuffer(protocol.txCont, sequence)) - end + + # Setup everything as defined per sequence setup(daq, sequence) - protocol.seqMeasState = SequenceMeasState(daq, sequence) - if protocol.params.saveTemperatureData - push!(deviceBuffer, TemperatureBuffer(getTemperatureSensor(scanner_), acqNumFrames(protocol.params.sequence))) - end - protocol.seqMeasState.deviceBuffers = deviceBuffer + # Now for the buffer chain we want to reinterpret periods to frames + # This has to happen after the RedPitaya sequence is set, as that code repeats the full sequence for each frame + acqNumFrames(sequence, acqNumFrames(sequence) * periodsPerFrame(daq.rpc)) + setupRx(daq, daq.decimation, samplesPerPeriod(daq.rpc), 1) + + #if protocol.params.controlTx + # sequence = controlTx(protocol.txCont, sequence) + # push!(deviceBuffer, TxDAQControllerBuffer(protocol.txCont, sequence)) + #end + + protocol.seqMeasState = SequenceMeasState(protocol) + #if protocol.params.saveTemperatureData + # push!(deviceBuffer, TemperatureBuffer(getTemperatureSensor(scanner_), acqNumFrames(protocol.params.sequence))) + #end + #protocol.seqMeasState.deviceBuffers = deviceBuffer protocol.seqMeasState.producer = @tspawnat scanner_.generalParams.producerThreadID asyncProducer(protocol.seqMeasState.channel, protocol, sequence) bind(protocol.seqMeasState.channel, protocol.seqMeasState.producer) protocol.seqMeasState.consumer = @tspawnat scanner_.generalParams.consumerThreadID asyncConsumer(protocol.seqMeasState) return protocol.seqMeasState end +function SequenceMeasState(protocol::MPSMeasurementProtocol) + sequence = protocol.sequence + daq = getDAQ(scanner(protocol)) + + numFrames = acqNumFrames(sequence) + + # Prepare buffering structures + @debug "Allocating buffer for $numFrames frames" + bufferSize = (rxNumSamplingPoints(sequence), length(rxChannels(sequence)), 1, numFrames) + buffer = MmapFrameBuffer(protocol, "meas.bin", Float32, bufferSize) + channel = Channel{channelType(daq)}(32) + + buffer = FrameSplitterBuffer(daq, [buffer]) + + deviceBuffer = DeviceBuffer[] + if protocol.params.controlTx + sequence = controlTx(protocol.txCont, sequence) + push!(deviceBuffer, TxDAQControllerBuffer(protocol.txCont, sequence)) + end + #if protocol.params.saveTemperatureData + # push!(deviceBuffer, TemperatureBuffer(getTemperatureSensor(scanner_), acqNumFrames(protocol.params.sequence))) + #end + + return SequenceMeasState(numFrames, channel, nothing, nothing, AsyncBuffer(buffer, daq), deviceBuffer, asyncMeasType(sequence)) +end + function cleanup(protocol::MPSMeasurementProtocol) # NOP @@ -298,8 +332,13 @@ handleEvent(protocol::MPSMeasurementProtocol, event::FinishedAckEvent) = protoco function handleEvent(protocol::MPSMeasurementProtocol, event::DatasetStoreStorageRequestEvent) store = event.datastore scanner = protocol.scanner + sequence = protocol.sequence mdf = event.mdf + data = read(protocol.protocolMeasState, MeasurementBuffer) + data = reshape(data, rxNumSamplingPoints(sequence), length(rxChannels(sequence)), :, protocol.params.fgFrames + protocol.params.bgFrames * protocol.params.measureBackground) + acqNumFrames(sequence, size(data, 4)) + offsets = protocol.offsetfields if protocol.params.sortPatches @@ -325,9 +364,9 @@ function handleEvent(protocol::MPSMeasurementProtocol, event::DatasetStoreStorag # All periods in one frame (should) have same offset offsets = reshape(offsets, protocol.params.dfPeriodsPerOffset, :, size(offsets, 2))[1, :, :] offsets = reshape(offsets, protocol.calibsize..., :) # make calib size "visible" to storing function - filename = saveasMDF(store, scanner, protocol.sequence, data, offsets, isBGFrame, mdf, storeAsSystemMatrix=protocol.params.saveAsSystemMatrix, drivefield = drivefield, temperatures = temperature, applied = appliedField) + filename = saveasMDF(store, scanner, sequence, data, offsets, isBGFrame, mdf, storeAsSystemMatrix=protocol.params.saveAsSystemMatrix, drivefield = drivefield, temperatures = temperature, applied = appliedField) else - filename = saveasMDF(store, scanner, protocol.sequence, data, isBGFrame, mdf, drivefield = drivefield, temperatures = temperature, applied = appliedField) + filename = saveasMDF(store, scanner, sequence, data, isBGFrame, mdf, drivefield = drivefield, temperatures = temperature, applied = appliedField) end @info "The measurement was saved at `$filename`." put!(protocol.biChannel, StorageSuccessEvent(filename)) From 487741417a85d1b83a43af99578c02d13bcc2e6f Mon Sep 17 00:00:00 2001 From: nHackel Date: Thu, 15 Feb 2024 16:50:38 +0100 Subject: [PATCH 081/168] Init working mps buffer --- src/Protocols/MPSMeasurementProtocol.jl | 50 +++++++++++++++++++------ 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/src/Protocols/MPSMeasurementProtocol.jl b/src/Protocols/MPSMeasurementProtocol.jl index 0856b058..3106dc45 100644 --- a/src/Protocols/MPSMeasurementProtocol.jl +++ b/src/Protocols/MPSMeasurementProtocol.jl @@ -253,12 +253,12 @@ function SequenceMeasState(protocol::MPSMeasurementProtocol) # Prepare buffering structures @debug "Allocating buffer for $numFrames frames" - bufferSize = (rxNumSamplingPoints(sequence), length(rxChannels(sequence)), 1, numFrames) + bufferSize = (rxNumSamplingPoints(sequence), length(rxChannels(sequence)), 1, length(protocol.patchPermutation)) buffer = MmapFrameBuffer(protocol, "meas.bin", Float32, bufferSize) channel = Channel{channelType(daq)}(32) - - buffer = FrameSplitterBuffer(daq, [buffer]) + buffer = MPSBuffer(buffer, protocol.patchPermutation, 1, length(protocol.patchPermutation)) + deviceBuffer = DeviceBuffer[] if protocol.params.controlTx sequence = controlTx(protocol.txCont, sequence) @@ -271,6 +271,32 @@ function SequenceMeasState(protocol::MPSMeasurementProtocol) return SequenceMeasState(numFrames, channel, nothing, nothing, AsyncBuffer(buffer, daq), deviceBuffer, asyncMeasType(sequence)) end +mutable struct MPSBuffer <: IntermediateBuffer + target::StorageBuffer + permutation::Vector{Int64} + counter::Int64 + total::Int64 +end +function push!(mpsBuffer::MPSBuffer, frames::Array{T,4}) where T + from = nothing + to = nothing + for i = 1:size(frames, 4) + frameIdx = div(mpsBuffer.counter - 1, mpsBuffer.total) + 1 + patchCounter = mod1(mpsBuffer.counter, mpsBuffer.total) + patchIdx = findfirst(x-> x == patchCounter, mpsBuffer.permutation) + if !isnothing(from) + to = insert!(target, frameIdx * mpsBuffer.total + patchIdx, frames[:, :, :, i]) + mpsBuffer.counter += 1 + end + end + if !isnothing(from) && !isnothing(to) + return (start = from, stop = to) + else + return nothing + end +end +sinks!(buffer::MPSBuffer, sinks::Vector{SinkBuffer}) = sinks!(buffer.target, sinks) + function cleanup(protocol::MPSMeasurementProtocol) # NOP @@ -341,15 +367,15 @@ function handleEvent(protocol::MPSMeasurementProtocol, event::DatasetStoreStorag offsets = protocol.offsetfields - if protocol.params.sortPatches - data = data[:, :, protocol.patchPermutation, :] - offsets = offsets[protocol.patchPermutation, :] - else - # Just remove "dead" patches - validPatches = filter(in(protocol.patchPermutation), collect(1:size(data, 3))) - data = data[:, :, validPatches, :] - offsets = offsets[validPatches, :] - end + #if protocol.params.sortPatches + # data = data[:, :, protocol.patchPermutation, :] + # offsets = offsets[protocol.patchPermutation, :] + #else + # # Just remove "dead" patches + # validPatches = filter(in(protocol.patchPermutation), collect(1:size(data, 3))) + # data = data[:, :, validPatches, :] + # offsets = offsets[validPatches, :] + #end isBGFrame = measIsBGFrame(protocol.protocolMeasState) drivefield = read(protocol.protocolMeasState, DriveFieldBuffer) From 2bc9ccc4998f1aedcfaf5ee2c835da74272ec8e7 Mon Sep 17 00:00:00 2001 From: nHackel Date: Thu, 15 Feb 2024 17:47:27 +0100 Subject: [PATCH 082/168] Remove MMapBuffer and merge with FrameBuffer --- .../RobotBasedSystemMatrixProtocol.jl | 2 +- src/Protocols/Storage/ChainableBuffer.jl | 46 ++++++------------- src/Protocols/Storage/ProducerConsumer.jl | 2 +- 3 files changed, 16 insertions(+), 34 deletions(-) diff --git a/src/Protocols/RobotBasedSystemMatrixProtocol.jl b/src/Protocols/RobotBasedSystemMatrixProtocol.jl index b81905aa..f9c5c4df 100644 --- a/src/Protocols/RobotBasedSystemMatrixProtocol.jl +++ b/src/Protocols/RobotBasedSystemMatrixProtocol.jl @@ -346,7 +346,7 @@ function asyncConsumer(channel::Channel, protocol::RobotBasedSystemMatrixProtoco end sinks = StorageBuffer[] - push!(sinks, SimpleFrameBuffer(1, view(calib.signals, :, :, :, startIdx:stopIdx))) + push!(sinks, FrameBuffer(1, view(calib.signals, :, :, :, startIdx:stopIdx))) sequence = protocol.params.sequence if protocol.params.controlTx sequence = protocol.contSequence diff --git a/src/Protocols/Storage/ChainableBuffer.jl b/src/Protocols/Storage/ChainableBuffer.jl index 22f15af2..21cd205f 100644 --- a/src/Protocols/Storage/ChainableBuffer.jl +++ b/src/Protocols/Storage/ChainableBuffer.jl @@ -5,7 +5,7 @@ mutable struct AverageBuffer{T} <: IntermediateBuffer where {T<:Number} end AverageBuffer(buffer::StorageBuffer, samples, channels, periods, avgFrames) = AverageBuffer{Float32}(buffer, zeros(Float32, samples, channels, periods, avgFrames), 1) AverageBuffer(buffer::StorageBuffer, sequence::Sequence) = AverageBuffer(buffer, rxNumSamplesPerPeriod(sequence), length(rxChannels(sequence)), acqNumPeriodsPerFrame(sequence), acqNumFrameAverages(sequence)) -function push!(avgBuffer::AverageBuffer{T}, frames::Array{T,4}) where {T<:Number} +function push!(avgBuffer::AverageBuffer{T}, frames::AbstractArray{T,4}) where {T<:Number} #setIndex - 1 = how many frames were written to the buffer # Compute how many frames there will be @@ -49,61 +49,43 @@ sinks!(buffer::AverageBuffer, sinks::Vector{SinkBuffer}) = sinks!(buffer.target, abstract type MeasurementBuffer <: SequenceBuffer end # TODO Error handling? Throw own error or crash with index error -mutable struct SimpleFrameBuffer{A<: AbstractArray{Float32, 4}} <: MeasurementBuffer +mutable struct FrameBuffer{A<: AbstractArray{Float32, 4}} <: MeasurementBuffer nextFrame::Integer data::A end -function SimpleFrameBuffer(sequence::Sequence) +function FrameBuffer(sequence::Sequence) numFrames = acqNumFrames(sequence) rxNumSamplingPoints = rxNumSamplesPerPeriod(sequence) numPeriods = acqNumPeriodsPerFrame(sequence) numChannel = length(rxChannels(sequence)) buffer = zeros(Float32, rxNumSamplingPoints, numChannel, numPeriods, numFrames) - return SimpleFrameBuffer(1, buffer) + return FrameBuffer(1, buffer) end -function insert!(buffer::SimpleFrameBuffer, from::Integer, frames::Array{Float32,4}) - to = from + size(frames, 4) - 1 - buffer.data[:, :, :, from:to] = frames - return to -end -function push!(buffer::SimpleFrameBuffer, frames::Array{Float32,4}) - from = buffer.nextFrame - to = insert!(buffer, from, frames) - buffer.nextFrame = to + 1 - return (start = from, stop = to) -end -read(buffer::SimpleFrameBuffer) = buffer.data -index(buffer::SimpleFrameBuffer) = buffer.nextFrame - -mutable struct MmapFrameBuffer{T, A<:AbstractArray{T, 4}} <: MeasurementBuffer - nextFrame::Int64 - data::A -end -function MmapFrameBuffer(protocol::Protocol, file::String, sequence::Sequence) +function FrameBuffer(protocol::Protocol, file::String, sequence::Sequence) numFrames = acqNumFrames(sequence) rxNumSamplingPoints = rxNumSamplesPerPeriod(sequence) numPeriods = acqNumPeriodsPerFrame(sequence) numChannel = length(rxChannels(sequence)) - return MmapFrameBuffer(protocol, file, Float32, (rxNumSamplingPoints, numChannel, numPeriods, numFrames)) + return FrameBuffer(protocol, file, Float32, (rxNumSamplingPoints, numChannel, numPeriods, numFrames)) end -function MmapFrameBuffer(protocol::Protocol, file::String, args...) +function FrameBuffer(protocol::Protocol, file::String, args...) mapped = mmap!(protocol, file, args...) - return MmapFrameBuffer(1, mapped) + return FrameBuffer(1, mapped) end -function insert!(buffer::MmapFrameBuffer, from::Integer, frames::Array{Float32,4}) + +function insert!(buffer::FrameBuffer, from::Integer, frames::AbstractArray{Float32,4}) to = from + size(frames, 4) - 1 buffer.data[:, :, :, from:to] = frames - Mmap.sync!(buffer.data) return to end -function push!(buffer::MmapFrameBuffer, frames::Array{Float32,4}) +function push!(buffer::FrameBuffer, frames::AbstractArray{Float32,4}) from = buffer.nextFrame to = insert!(buffer, from, frames) buffer.nextFrame = to + 1 return (start = from, stop = to) end -read(buffer::MmapFrameBuffer) = buffer.data -index(buffer::MmapFrameBuffer) = buffer.nextFrame +read(buffer::FrameBuffer) = buffer.data +index(buffer::FrameBuffer) = buffer.nextFrame abstract type FieldBuffer <: SequenceBuffer end mutable struct DriveFieldBuffer{A <: AbstractArray{ComplexF64, 4}} <: FieldBuffer @@ -112,7 +94,7 @@ mutable struct DriveFieldBuffer{A <: AbstractArray{ComplexF64, 4}} <: FieldBuffe cont::ControlSequence end function insert!(buffer::DriveFieldBuffer, from::Integer, frames::Array{ComplexF64,4}) - # TODO duplicate to SimpleFrameBuffer + # TODO duplicate to FrameBuffer to = from + size(frames, 4) - 1 buffer.data[:, :, :, from:to] = frames return to diff --git a/src/Protocols/Storage/ProducerConsumer.jl b/src/Protocols/Storage/ProducerConsumer.jl index d0c6ea3c..a397ebd9 100644 --- a/src/Protocols/Storage/ProducerConsumer.jl +++ b/src/Protocols/Storage/ProducerConsumer.jl @@ -19,7 +19,7 @@ function SequenceMeasState(daq::RedPitayaDAQ, sequence::Sequence, sequenceBuffer # Prepare buffering structures @debug "Allocating buffer for $numFrames frames" - buffer = SimpleFrameBuffer(sequence) + buffer = FrameBuffer(sequence) if acqNumFrameAverages(sequence) > 1 buffer = AverageBuffer(buffer, sequence) end From 4c878d58a85c9122f17848a3f4a680b53cf356aa Mon Sep 17 00:00:00 2001 From: nHackel Date: Fri, 23 Feb 2024 11:47:21 +0100 Subject: [PATCH 083/168] Init averaging for MpsBuffer --- src/Protocols/MPSMeasurementProtocol.jl | 46 +++++++++++++++++------- src/Protocols/Storage/ChainableBuffer.jl | 6 ++++ 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/src/Protocols/MPSMeasurementProtocol.jl b/src/Protocols/MPSMeasurementProtocol.jl index 3106dc45..7a9ddba1 100644 --- a/src/Protocols/MPSMeasurementProtocol.jl +++ b/src/Protocols/MPSMeasurementProtocol.jl @@ -10,7 +10,7 @@ Base.@kwdef mutable struct MPSMeasurementProtocolParams <: ProtocolParams "If set the tx amplitude and phase will be set with control steps" controlTx::Bool = false "If unset no background measurement will be taken" - measureBackground::Bool = true + measureBackground::Bool = false "Remember background measurement" rememberBGMeas::Bool = false "Tracer that is being used for the measurement" @@ -27,6 +27,7 @@ Base.@kwdef mutable struct MPSMeasurementProtocolParams <: ProtocolParams # TODO: This is only for 1D MPS systems for now "Number of periods per offset of the MPS offset measurement. Overwrites parts of the sequence definition." dfPeriodsPerOffset::Integer = 2 + averagePeriodsPerOffset::Bool = true end function MPSMeasurementProtocolParams(dict::Dict, scanner::MPIScanner) sequence_ = nothing @@ -60,7 +61,7 @@ Base.@kwdef mutable struct MPSMeasurementProtocol <: Protocol sequence::Union{Sequence, Nothing} = nothing offsetfields::Union{Matrix{Float64}, Nothing} = nothing - patchPermutation::Vector{Int64} = Int64[] + patchPermutation::Vector{Union{Int64, Nothing}} = Union{Int64, Nothing}[] calibsize::Vector{Int64} = Int64[] bgMeas::Array{Float32, 4} = zeros(Float32,0,0,0,0) @@ -97,8 +98,18 @@ function _init(protocol::MPSMeasurementProtocol) try seq, perm, offsets, calibsize = prepareProtocolSequences(protocol.params.sequence, getDAQ(scanner(protocol)); numPeriodsPerPatch = protocol.params.dfPeriodsPerOffset) + + # For each patch assign nothing if invalid or otherwise index in "proper" frame + temp = Vector{Union{Int64, Nothing}}(nothing, acqNumPeriodsPerFrame(seq)) + if protocol.params.sortPatches + part = protocol.params.averagePeriodsPerOffset ? protocol.params.dfPeriodsPerOffset : 1 + for (i, patches) in enumerate(Iterators.partition(perm, part)) + temp[patches] .= i + end + end + protocol.sequence = seq - protocol.patchPermutation = perm + protocol.patchPermutation = temp protocol.offsetfields = ustrip.(u"T", offsets) # TODO make robust protocol.calibsize = calibsize catch e @@ -253,11 +264,14 @@ function SequenceMeasState(protocol::MPSMeasurementProtocol) # Prepare buffering structures @debug "Allocating buffer for $numFrames frames" - bufferSize = (rxNumSamplingPoints(sequence), length(rxChannels(sequence)), 1, length(protocol.patchPermutation)) - buffer = MmapFrameBuffer(protocol, "meas.bin", Float32, bufferSize) + numValidPatches = length(filter(x->!isnothing(x), protocol.patchPermutation)) + averages = protocol.params.averagePeriodsPerOffset ? protocol.params.dfPeriodsPerOffset : 1 + numFrames = div(numValidPatches, averages) + bufferSize = (rxNumSamplingPoints(sequence), length(rxChannels(sequence)), 1, numFrames) + buffer = FrameBuffer(protocol, "meas.bin", Float32, bufferSize) channel = Channel{channelType(daq)}(32) - buffer = MPSBuffer(buffer, protocol.patchPermutation, 1, length(protocol.patchPermutation)) + buffer = MPSBuffer(buffer, protocol.patchPermutation, protocol.params.sortPatches, numFrames, 1, acqNumPeriodsPerFrame(sequence)) deviceBuffer = DeviceBuffer[] if protocol.params.controlTx @@ -273,7 +287,9 @@ end mutable struct MPSBuffer <: IntermediateBuffer target::StorageBuffer - permutation::Vector{Int64} + permutation::Vector{Union{Int64, Nothing}} + sort::Bool + average::Int64 counter::Int64 total::Int64 end @@ -281,13 +297,17 @@ function push!(mpsBuffer::MPSBuffer, frames::Array{T,4}) where T from = nothing to = nothing for i = 1:size(frames, 4) - frameIdx = div(mpsBuffer.counter - 1, mpsBuffer.total) + 1 + frameIdx = div(mpsBuffer.counter - 1, mpsBuffer.total) patchCounter = mod1(mpsBuffer.counter, mpsBuffer.total) - patchIdx = findfirst(x-> x == patchCounter, mpsBuffer.permutation) - if !isnothing(from) - to = insert!(target, frameIdx * mpsBuffer.total + patchIdx, frames[:, :, :, i]) - mpsBuffer.counter += 1 + patchIdx = mpsBuffer.permutation[patchCounter] + if !isnothing(patchIdx) + if mpsBuffer.average > 1 + to = insert!(+, mpsBuffer.target, frameIdx * mpsBuffer.total + patchIdx, view(frames, :, :, :, i:i)./mpsBuffer.average) + else + to = insert!(mpsBuffer.target, frameIdx * mpsBuffer.total + patchIdx, view(frames, :, :, :, i:i)) + end end + mpsBuffer.counter += 1 end if !isnothing(from) && !isnothing(to) return (start = from, stop = to) @@ -377,7 +397,7 @@ function handleEvent(protocol::MPSMeasurementProtocol, event::DatasetStoreStorag # offsets = offsets[validPatches, :] #end - isBGFrame = measIsBGFrame(protocol.protocolMeasState) + isBGFrame = reduce(&, reshape(measIsBGFrame(protocol.protocolMeasState), size(data, 3), :), dims = 1)[1, :] drivefield = read(protocol.protocolMeasState, DriveFieldBuffer) appliedField = read(protocol.protocolMeasState, TxDAQControllerBuffer) temperature = read(protocol.protocolMeasState, TemperatureBuffer) diff --git a/src/Protocols/Storage/ChainableBuffer.jl b/src/Protocols/Storage/ChainableBuffer.jl index 21cd205f..dd469d06 100644 --- a/src/Protocols/Storage/ChainableBuffer.jl +++ b/src/Protocols/Storage/ChainableBuffer.jl @@ -73,9 +73,15 @@ function FrameBuffer(protocol::Protocol, file::String, args...) return FrameBuffer(1, mapped) end +function insert!(op, buffer::FrameBuffer, from::Integer, frames::AbstractArray{Float32, 4}) + to = from + size(frames, 4) - 1 + frames = op(frames, view(buffer.data, :, :, :, from:to)) + insert!(buffer, from, frames) +end function insert!(buffer::FrameBuffer, from::Integer, frames::AbstractArray{Float32,4}) to = from + size(frames, 4) - 1 buffer.data[:, :, :, from:to] = frames + buffer.nextFrame = to return to end function push!(buffer::FrameBuffer, frames::AbstractArray{Float32,4}) From 12940126be5111dbbba18e45b68ca2689f563a9b Mon Sep 17 00:00:00 2001 From: nHackel Date: Fri, 23 Feb 2024 14:11:26 +0100 Subject: [PATCH 084/168] Fix sorting and not sorting for MPSBuffer --- src/Protocols/MPSMeasurementProtocol.jl | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Protocols/MPSMeasurementProtocol.jl b/src/Protocols/MPSMeasurementProtocol.jl index 7a9ddba1..e579fefa 100644 --- a/src/Protocols/MPSMeasurementProtocol.jl +++ b/src/Protocols/MPSMeasurementProtocol.jl @@ -101,11 +101,13 @@ function _init(protocol::MPSMeasurementProtocol) # For each patch assign nothing if invalid or otherwise index in "proper" frame temp = Vector{Union{Int64, Nothing}}(nothing, acqNumPeriodsPerFrame(seq)) - if protocol.params.sortPatches - part = protocol.params.averagePeriodsPerOffset ? protocol.params.dfPeriodsPerOffset : 1 - for (i, patches) in enumerate(Iterators.partition(perm, part)) - temp[patches] .= i - end + if !protocol.params.sortPatches + perm = filter(in(perm), 1:acqNumPeriodsPerFrame(seq)) + end + # Same target for all frames to be averaged + part = protocol.params.averagePeriodsPerOffset ? protocol.params.dfPeriodsPerOffset : 1 + for (i, patches) in enumerate(Iterators.partition(perm, part)) + temp[patches] .= i end protocol.sequence = seq @@ -272,6 +274,9 @@ function SequenceMeasState(protocol::MPSMeasurementProtocol) channel = Channel{channelType(daq)}(32) buffer = MPSBuffer(buffer, protocol.patchPermutation, protocol.params.sortPatches, numFrames, 1, acqNumPeriodsPerFrame(sequence)) + + # TODO DriveFieldBuffer + buffer = FrameSplitterBuffer(daq, StorageBuffer[buffer]) deviceBuffer = DeviceBuffer[] if protocol.params.controlTx From dbb822224b64c9b195cabb191f22b253f877f7f5 Mon Sep 17 00:00:00 2001 From: nHackel Date: Fri, 23 Feb 2024 14:59:59 +0100 Subject: [PATCH 085/168] Fix calibOffset filtering for MPS protocol --- src/Protocols/MPSMeasurementProtocol.jl | 31 +++++++++++-------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/src/Protocols/MPSMeasurementProtocol.jl b/src/Protocols/MPSMeasurementProtocol.jl index e579fefa..31d79af8 100644 --- a/src/Protocols/MPSMeasurementProtocol.jl +++ b/src/Protocols/MPSMeasurementProtocol.jl @@ -24,9 +24,9 @@ Base.@kwdef mutable struct MPSMeasurementProtocolParams <: ProtocolParams "Flag if the measurement should be saved as a system matrix or not" saveAsSystemMatrix::Bool = true - # TODO: This is only for 1D MPS systems for now "Number of periods per offset of the MPS offset measurement. Overwrites parts of the sequence definition." dfPeriodsPerOffset::Integer = 2 + "If true all periods per offset are averaged" averagePeriodsPerOffset::Bool = true end function MPSMeasurementProtocolParams(dict::Dict, scanner::MPIScanner) @@ -273,7 +273,7 @@ function SequenceMeasState(protocol::MPSMeasurementProtocol) buffer = FrameBuffer(protocol, "meas.bin", Float32, bufferSize) channel = Channel{channelType(daq)}(32) - buffer = MPSBuffer(buffer, protocol.patchPermutation, protocol.params.sortPatches, numFrames, 1, acqNumPeriodsPerFrame(sequence)) + buffer = MPSBuffer(buffer, protocol.patchPermutation, numFrames, 1, acqNumPeriodsPerFrame(sequence)) # TODO DriveFieldBuffer buffer = FrameSplitterBuffer(daq, StorageBuffer[buffer]) @@ -293,7 +293,6 @@ end mutable struct MPSBuffer <: IntermediateBuffer target::StorageBuffer permutation::Vector{Union{Int64, Nothing}} - sort::Bool average::Int64 counter::Int64 total::Int64 @@ -390,17 +389,14 @@ function handleEvent(protocol::MPSMeasurementProtocol, event::DatasetStoreStorag data = reshape(data, rxNumSamplingPoints(sequence), length(rxChannels(sequence)), :, protocol.params.fgFrames + protocol.params.bgFrames * protocol.params.measureBackground) acqNumFrames(sequence, size(data, 4)) - offsets = protocol.offsetfields - - #if protocol.params.sortPatches - # data = data[:, :, protocol.patchPermutation, :] - # offsets = offsets[protocol.patchPermutation, :] - #else - # # Just remove "dead" patches - # validPatches = filter(in(protocol.patchPermutation), collect(1:size(data, 3))) - # data = data[:, :, validPatches, :] - # offsets = offsets[validPatches, :] - #end + offsetPerm = zeros(Int64, size(data, 3)) + for (index, patch) in enumerate(protocol.patchPermutation) + if !isnothing(patch) + offsetPerm[patch] = index + end + end + offsets = protocol.offsetfields[offsetPerm, :] + isBGFrame = reduce(&, reshape(measIsBGFrame(protocol.protocolMeasState), size(data, 3), :), dims = 1)[1, :] drivefield = read(protocol.protocolMeasState, DriveFieldBuffer) @@ -410,10 +406,11 @@ function handleEvent(protocol::MPSMeasurementProtocol, event::DatasetStoreStorag filename = nothing if protocol.params.saveAsSystemMatrix - isBGFrame = repeat(isBGFrame, inner = div(size(data, 3), protocol.params.dfPeriodsPerOffset)) - data = reshape(data, size(data, 1), size(data, 2), protocol.params.dfPeriodsPerOffset, :) + periodsPerOffset = protocol.params.averagePeriodsPerOffset ? 1 : protocol.params.dfPeriodsPerOffset + isBGFrame = repeat(isBGFrame, inner = div(size(data, 3), periodsPerOffset)) + data = reshape(data, size(data, 1), size(data, 2), periodsPerOffset, :) # All periods in one frame (should) have same offset - offsets = reshape(offsets, protocol.params.dfPeriodsPerOffset, :, size(offsets, 2))[1, :, :] + offsets = reshape(offsets, periodsPerOffset, :, size(offsets, 2))[1, :, :] offsets = reshape(offsets, protocol.calibsize..., :) # make calib size "visible" to storing function filename = saveasMDF(store, scanner, sequence, data, offsets, isBGFrame, mdf, storeAsSystemMatrix=protocol.params.saveAsSystemMatrix, drivefield = drivefield, temperatures = temperature, applied = appliedField) else From 3422ac9d764c6182fe5c37dee36fd48cb7c38f55 Mon Sep 17 00:00:00 2001 From: nHackel Date: Fri, 23 Feb 2024 17:28:21 +0100 Subject: [PATCH 086/168] WIP Ref Buffers for MPSProtocol (not working atm) --- src/Protocols/MPSMeasurementProtocol.jl | 69 ++++++++++++------------ src/Protocols/Storage/ChainableBuffer.jl | 46 ++++++++++++++++ 2 files changed, 79 insertions(+), 36 deletions(-) diff --git a/src/Protocols/MPSMeasurementProtocol.jl b/src/Protocols/MPSMeasurementProtocol.jl index 31d79af8..1fc0a0e2 100644 --- a/src/Protocols/MPSMeasurementProtocol.jl +++ b/src/Protocols/MPSMeasurementProtocol.jl @@ -230,28 +230,8 @@ function measurement(protocol::MPSMeasurementProtocol) end function asyncMeasurement(protocol::MPSMeasurementProtocol) - scanner_ = scanner(protocol) - sequence = protocol.sequence - daq = getDAQ(scanner_) - deviceBuffer = DeviceBuffer[] - - # Setup everything as defined per sequence - setup(daq, sequence) - # Now for the buffer chain we want to reinterpret periods to frames - # This has to happen after the RedPitaya sequence is set, as that code repeats the full sequence for each frame - acqNumFrames(sequence, acqNumFrames(sequence) * periodsPerFrame(daq.rpc)) - setupRx(daq, daq.decimation, samplesPerPeriod(daq.rpc), 1) - - #if protocol.params.controlTx - # sequence = controlTx(protocol.txCont, sequence) - # push!(deviceBuffer, TxDAQControllerBuffer(protocol.txCont, sequence)) - #end - - protocol.seqMeasState = SequenceMeasState(protocol) - #if protocol.params.saveTemperatureData - # push!(deviceBuffer, TemperatureBuffer(getTemperatureSensor(scanner_), acqNumFrames(protocol.params.sequence))) - #end - #protocol.seqMeasState.deviceBuffers = deviceBuffer + scanner_ = scanner(protocol) + sequence, protocol.seqMeasState = SequenceMeasState(protocol) protocol.seqMeasState.producer = @tspawnat scanner_.generalParams.producerThreadID asyncProducer(protocol.seqMeasState.channel, protocol, sequence) bind(protocol.seqMeasState.channel, protocol.seqMeasState.producer) protocol.seqMeasState.consumer = @tspawnat scanner_.generalParams.consumerThreadID asyncConsumer(protocol.seqMeasState) @@ -261,33 +241,50 @@ end function SequenceMeasState(protocol::MPSMeasurementProtocol) sequence = protocol.sequence daq = getDAQ(scanner(protocol)) + deviceBuffer = DeviceBuffer[] + + + # Setup everything as defined per sequence + if protocol.params.controlTx + sequence = controlTx(protocol.txCont, sequence) + push!(deviceBuffer, TxDAQControllerBuffer(protocol.txCont, sequence)) + end + setup(daq, sequence) + + # Now for the buffer chain we want to reinterpret periods to frames + # This has to happen after the RedPitaya sequence is set, as that code repeats the full sequence for each frame + acqNumFrames(protocol.sequence, acqNumFrames(protocol.sequence) * periodsPerFrame(daq.rpc)) + setupRx(daq, daq.decimation, samplesPerPeriod(daq.rpc), 1) - numFrames = acqNumFrames(sequence) + numFrames = acqNumFrames(protocol.sequence) - # Prepare buffering structures + # Prepare buffering structures: + # RedPitaya-> MPSBuffer -> Splitter --> FrameBuffer{Mmap} + # |-> DriveFieldBuffer @debug "Allocating buffer for $numFrames frames" numValidPatches = length(filter(x->!isnothing(x), protocol.patchPermutation)) averages = protocol.params.averagePeriodsPerOffset ? protocol.params.dfPeriodsPerOffset : 1 numFrames = div(numValidPatches, averages) - bufferSize = (rxNumSamplingPoints(sequence), length(rxChannels(sequence)), 1, numFrames) + bufferSize = (rxNumSamplingPoints(protocol.sequence), length(rxChannels(protocol.sequence)), 1, numFrames) buffer = FrameBuffer(protocol, "meas.bin", Float32, bufferSize) - channel = Channel{channelType(daq)}(32) - buffer = MPSBuffer(buffer, protocol.patchPermutation, numFrames, 1, acqNumPeriodsPerFrame(sequence)) + buffers = StorageBuffer[buffer] + + if protocol.params.controlTx + len = length(keys(sequence.simpleChannel)) + push!(buffers, DriveFieldBuffer(1, zeros(ComplexF64, len, len, 1, numFrames), sequence)) + end - # TODO DriveFieldBuffer buffer = FrameSplitterBuffer(daq, StorageBuffer[buffer]) - + buffer = MPSBuffer(buffer, protocol.patchPermutation, numFrames, 1, acqNumPeriodsPerFrame(protocol.sequence)) + + channel = Channel{channelType(daq)}(32) deviceBuffer = DeviceBuffer[] - if protocol.params.controlTx - sequence = controlTx(protocol.txCont, sequence) - push!(deviceBuffer, TxDAQControllerBuffer(protocol.txCont, sequence)) + if protocol.params.saveTemperatureData + push!(deviceBuffer, TemperatureBuffer(getTemperatureSensor(scanner(protocol)), numFrames)) end - #if protocol.params.saveTemperatureData - # push!(deviceBuffer, TemperatureBuffer(getTemperatureSensor(scanner_), acqNumFrames(protocol.params.sequence))) - #end - return SequenceMeasState(numFrames, channel, nothing, nothing, AsyncBuffer(buffer, daq), deviceBuffer, asyncMeasType(sequence)) + return sequence, SequenceMeasState(numFrames, channel, nothing, nothing, AsyncBuffer(buffer, daq), deviceBuffer, asyncMeasType(protocol.sequence)) end mutable struct MPSBuffer <: IntermediateBuffer diff --git a/src/Protocols/Storage/ChainableBuffer.jl b/src/Protocols/Storage/ChainableBuffer.jl index dd469d06..f27322a3 100644 --- a/src/Protocols/Storage/ChainableBuffer.jl +++ b/src/Protocols/Storage/ChainableBuffer.jl @@ -99,10 +99,16 @@ mutable struct DriveFieldBuffer{A <: AbstractArray{ComplexF64, 4}} <: FieldBuffe data::A cont::ControlSequence end +function insert!(op, buffer::DriveFieldBuffer, from::Integer, frames::AbstractArray{ComplexF64, 4}) + to = from + size(frames, 4) - 1 + frames = op(frames, view(buffer.data, :, :, :, from:to)) + insert!(buffer, from, frames) +end function insert!(buffer::DriveFieldBuffer, from::Integer, frames::Array{ComplexF64,4}) # TODO duplicate to FrameBuffer to = from + size(frames, 4) - 1 buffer.data[:, :, :, from:to] = frames + buffer.nextFrame = to return to end function push!(buffer::DriveFieldBuffer, frames::Array{ComplexF64,4}) @@ -111,6 +117,8 @@ function push!(buffer::DriveFieldBuffer, frames::Array{ComplexF64,4}) buffer.nextFrame = to + 1 return (start = from, stop = to) end +insert!(op, buffer::DriveFieldBuffer, from::Integer, frames::Array{Float32,4}) = insert!(op, buffer, from, calcFieldsFromRef(buffer.cont, frames)) +insert!(buffer::DriveFieldBuffer, from::Integer, frames::Array{Float32,4}) = insert!(buffer, from, calcFieldsFromRef(buffer.cont, frames)) push!(buffer::DriveFieldBuffer, frames::Array{Float32,4}) = push!(buffer, calcFieldsFromRef(buffer.cont, frames)) read(buffer::DriveFieldBuffer) = buffer.data index(buffer::DriveFieldBuffer) = buffer.nextFrame @@ -138,6 +146,44 @@ function push!(buffer::FrameSplitterBuffer, frames) end return result end +function insert!(buffer::FrameSplitterBuffer, from, frames) + uMeas, uRef = retrieveMeasAndRef!(frames, buffer.daq) + result = nothing + if !isnothing(uMeas) + for buf in buffer.targets + measSinks = length(sinks(buf, MeasurementBuffer)) + fieldSinks = length(sinks(buf, DriveFieldBuffer)) + if measSinks > 0 && fieldSinks == 0 + # Return latest measurement result + result = insert!(buf, from, uMeas) + elseif measSinks == 0 && fieldSinks > 0 + insert!(buf, from, uRef) + else + @warn "Unexpected sink combination $(typeof.(sinks(buf)))" + end + end + end + return result +end +function insert!(op, buffer::FrameSplitterBuffer, from, frames) + uMeas, uRef = retrieveMeasAndRef!(frames, buffer.daq) + result = nothing + if !isnothing(uMeas) + for buf in buffer.targets + measSinks = length(sinks(buf, MeasurementBuffer)) + fieldSinks = length(sinks(buf, DriveFieldBuffer)) + if measSinks > 0 && fieldSinks == 0 + # Return latest measurement result + result = insert!(op, buf, from, uMeas) + elseif measSinks == 0 && fieldSinks > 0 + insert!(op, buf, from, uRef) + else + @warn "Unexpected sink combination $(typeof.(sinks(buf)))" + end + end + end + return result +end function sinks!(buffer::FrameSplitterBuffer, sinks::Vector{SinkBuffer}) for buf in buffer.targets sinks!(buf, sinks) From 61d5eb48c6a39bc9988f0a8c062ae7f85f3eadd2 Mon Sep 17 00:00:00 2001 From: Justin Ackers Date: Tue, 9 Apr 2024 15:59:00 +0200 Subject: [PATCH 087/168] fix progress and time estimation --- src/Protocols/MPSMeasurementProtocol.jl | 37 +++++++++++++++++++------ 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/src/Protocols/MPSMeasurementProtocol.jl b/src/Protocols/MPSMeasurementProtocol.jl index 1fc0a0e2..ff971425 100644 --- a/src/Protocols/MPSMeasurementProtocol.jl +++ b/src/Protocols/MPSMeasurementProtocol.jl @@ -125,13 +125,31 @@ function timeEstimate(protocol::MPSMeasurementProtocol) est = "Unknown" if !isnothing(sequence(protocol)) params = protocol.params - seq = params.sequence - totalFrames = (params.fgFrames + params.bgFrames) * acqNumFrameAverages(seq) - samplesPerFrame = rxNumSamplingPoints(seq) * acqNumAverages(seq) * acqNumPeriodsPerFrame(seq) - totalTime = (samplesPerFrame * totalFrames) / (125e6/(txBaseFrequency(seq)/rxSamplingRate(seq))) - time = totalTime * 1u"s" - est = string(time) - @info "The estimated duration is $est s." + seq = sequence(protocol) + fgFrames = params.fgFrames * acqNumFrameAverages(seq) + bgFrames = params.measureBackground*params.bgFrames * acqNumFrameAverages(seq) + txSamplesPerFrame = lcm(dfDivider(seq)) * size(protocol.patchPermutation, 1) + fgTime = (txSamplesPerFrame * fgFrames) / txBaseFrequency(seq) |> u"s" + bgTime = (txSamplesPerFrame * bgFrames) / txBaseFrequency(seq) |> u"s" + function timeFormat(t) + v = ustrip(u"s",t) + if v>3600 + x = Int((v%3600)÷60) + return "$(Int(v÷3600)):$(if x<10; " " else "" end)$(x) h" + elseif v>60 + x = round(v%60,digits=1) + return "$(Int(v÷60)):$(if x<10; " " else "" end)$(x) min" + elseif v>0.5 + return "$(round(v,digits=2)) s" + elseif v>0.5e-3 + return "$(round(v*1e3,digits=2)) ms" + else + return "$(round(v*1e6,digits=2)) µs" + end + end + perc_wait = round(Int,sum(isnothing.(protocol.patchPermutation))/size(protocol.patchPermutation,1)*100) + est = "FG: $(timeFormat(fgTime)) ($(perc_wait)% waiting), BG: $(timeFormat(bgTime))" + @info "The estimated duration is FG: $fgTime ($(perc_wait)% waiting), BG: $bgTime." end return est end @@ -180,7 +198,7 @@ function performMeasurement(protocol::MPSMeasurementProtocol) acqNumFrames(sequence(protocol), protocol.params.fgFrames) @debug "Starting foreground measurement." - protocol.unit = "Frames" + protocol.unit = "Offsets" measurement(protocol) deviceBuffers = protocol.seqMeasState.deviceBuffers push!(protocol.protocolMeasState, vcat(sinks(protocol.seqMeasState.sequenceBuffer), isnothing(deviceBuffers) ? SinkBuffer[] : deviceBuffers), isBGMeas = false) @@ -366,7 +384,8 @@ function handleEvent(protocol::MPSMeasurementProtocol, event::ProgressQueryEvent reply = nothing if !isnothing(protocol.seqMeasState) framesTotal = protocol.seqMeasState.numFrames - framesDone = min(index(sink(protocol.seqMeasState.sequenceBuffer, MeasurementBuffer)) - 1, framesTotal) + measFramesDone = protocol.seqMeasState.sequenceBuffer.target.counter-1 + framesDone = length(unique(protocol.patchPermutation[1:measFramesDone]))-1 reply = ProgressEvent(framesDone, framesTotal, protocol.unit, event) else reply = ProgressEvent(0, 0, "N/A", event) From 289601470a4b28462907a863de98a2241254383c Mon Sep 17 00:00:00 2001 From: Justin Ackers Date: Tue, 9 Apr 2024 15:59:45 +0200 Subject: [PATCH 088/168] reduce stepduration in case of averaging to allow more efficient wait times --- src/Devices/DAQ/RedPitayaDAQ.jl | 94 ++++++++++++++++++------ src/Protocols/MPSMeasurementProtocol.jl | 20 +++-- src/Protocols/Storage/ChainableBuffer.jl | 2 + 3 files changed, 86 insertions(+), 30 deletions(-) diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index c9cacd5c..80cc7aee 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -339,7 +339,7 @@ function setSequenceParams(daq::RedPitayaDAQ, luts::Vector{Union{Nothing, Array{ @add_batch batch samplesPerStep(daq.rpc) end daq.samplesPerStep = result[3][1] - + rampingSteps = 0 fractionSteps = 0 if !isempty(daq.rampingChannel) @@ -467,7 +467,7 @@ function getTiming(daq::RedPitayaDAQ) end -function prepareProtocolSequences(base::Sequence, daq::RedPitayaDAQ; numPeriodsPerPatch::Int64 = 1) +function prepareProtocolSequences(base::Sequence, daq::RedPitayaDAQ; numPeriodsPerOffset::Int64 = 1) cpy = deepcopy(base) # Prepare offset sequences @@ -482,10 +482,31 @@ function prepareProtocolSequences(base::Sequence, daq::RedPitayaDAQ; numPeriodsP end end - # Compute step duration, assumption step is held for df * numPeriodsPerPatch - stepfreq = 125e6/lcm(dfDivider(cpy))/numPeriodsPerPatch + stepfreq = 125e6/lcm(dfDivider(cpy)) + if stepfreq > 10e3 + if stepfreq/numPeriodsPerOffset > 10e3 + error("One period of the configured drivefield is only $(round(1/stepfreq*1e6,digits=4)) us long. To ensure that the RP can keep up with the offsets, please use a number of periods per offset that is a multiple of 50 and at least $(ceil(stepfreq/10e3/50)*50)!") + else + if (numPeriodsPerOffset/50 != numPeriodsPerOffset÷50) + error("One period of the configured drivefield is only $(round(1/stepfreq*1e6,digits=4)) us long. To ensure that the RP can keep up with the offsets, please use a number of periods per offset that is a multiple of 50!") + else + # if the DF period is shorter than 0.1ms (10kHz) we use 50 DF periods for every step, this allows us to use DF periods of up to 10kHz*50=500kHz and results in a worst case wait time inefficiency of 0.1ms*50 = 5ms + # it would be poossible to use other numbers for periodsPerStep, but this will probably work best for both extremes + periodsPerStep = 50 + stepfreq = stepfreq/periodsPerStep + end + end + else + # if a DF period is longer than 0.1ms we can just use a single step for every period, ensuring most efficient use of waittimes + periodsPerStep = 1 + end + + stepsPerOffset = numPeriodsPerOffset÷periodsPerStep + + # Compute step duration stepduration = (1/stepfreq) - + + @debug "prepareProtocolSequences: Stepduration: $stepduration, Stepfreq: $stepfreq" allSteps = nothing enables = nothing hbridges = nothing @@ -494,28 +515,55 @@ function prepareProtocolSequences(base::Sequence, daq::RedPitayaDAQ; numPeriodsP else offsets, allSteps, enables, hbridges, permutation = prepareOffsets(offsetVector, daq, cpy, stepduration) end - + numOffsets = length(first(allSteps)[2]) df = lcm(dfDivider(cpy)) - # Increasing the divider instead of repeating values allows the user to tune offset length s.t. the RP can keep up - divider = df * numOffsets * numPeriodsPerPatch - - # offsets and permutation refer to offsets without multiple df per period - # As the signals are held longer, we have to extend each offset and permutation for numPeriodsPerPatch - # s.t. the correct patches can be cut out later - if numPeriodsPerPatch > 1 - updatedPermutation = Int64[] - for patch in permutation - start = numPeriodsPerPatch * (patch - 1) + 1 - push!(updatedPermutation, map(x->start + x, 0:numPeriodsPerPatch-1)...) + + numMeasOffsets = length(permutation) + numWaitOffsets = numOffsets-numMeasOffsets + numPeriodsPerFrame = (numMeasOffsets * stepsPerOffset + numWaitOffsets) * periodsPerStep + + divider = df * numPeriodsPerFrame + + # offsets and permutation refer to offsets without multiple df per period and include every offset only once + # We need to perform two operations: + # 1. build an index mask that handles the repetition of offsets, such that offsets[mask] contains the desired repetitions of offsets (not the wait times) + stepRepetition = collect(1:numOffsets) + if numPeriodsPerOffset > 1 + + stepRepetition = Int[] + for i in 1:numOffsets + if i in permutation # if offset i is a offset that should be saved + append!(stepRepetition, repeat([i],stepsPerOffset)) # we have to repeat it by the amount of steps per Offset + else + push!(stepRepetition, i) # otherwise we just output it once + end end - permutation = updatedPermutation - offsets = repeat(offsets, inner = Tuple([i == 1 ? numPeriodsPerPatch : 1 for i = 1:ndims(offsets)])) + + # apply step repetition to permutation, every valid step is repeated stepsPerOffset times + updatedPermutation = repeat(permutation, inner=stepsPerOffset) + # to remove the repeated values, add 1 to all indizes starting from each value that is identical to its previous neighbor + sortedIdx = sortperm(updatedPermutation) + for i=2:length(updatedPermutation) + if updatedPermutation[sortedIdx[i]] == updatedPermutation[sortedIdx[i-1]] + updatedPermutation[sortedIdx[i:end]] .+= 1 + end + end + + offsets = offsets[stepRepetition,:] + + permutation = Int64[] + for patch in updatedPermutation + idx = (patch-1)*periodsPerStep+1:patch*periodsPerStep + append!(permutation, idx) + end + end + @debug "prepareProtocolSequences: Permutation after update" permutation offsets # Generate actual StepwiseElectricalChannel for (ch, steps) in allSteps - stepwise = StepwiseElectricalChannel(id = id(ch), divider = divider, values = identity.(steps), enable = enables[ch]) + stepwise = StepwiseElectricalChannel(id = id(ch), divider = divider, values = identity.(steps)[stepRepetition], enable = enables[ch][stepRepetition]) fieldMap[ch][id(stepwise)] = stepwise # Generate H-Bridge channels @@ -525,13 +573,13 @@ function prepareProtocolSequences(base::Sequence, daq::RedPitayaDAQ; numPeriodsP hbridgeValues = hbridges[ch] for (i, hbridgeChannel) in enumerate(hbridgeChannels) hsteps = map(x -> x[i], hbridgeValues) - hbridgeStepwise = StepwiseElectricalChannel(id = hbridgeChannel, divider = divider, values = hsteps, enable = fill(true, length(steps))) + hbridgeStepwise = StepwiseElectricalChannel(id = hbridgeChannel, divider = divider, values = hsteps[stepRepetition], enable = fill(true, length(stepRepetition))) fieldMap[ch][hbridgeChannel] = hbridgeStepwise end end end - return cpy, permutation, offsets, calibsize + return cpy, permutation, offsets, calibsize, numPeriodsPerFrame end function prepareHSwitchedOffsets(offsetVector::Vector{ProtocolOffsetElectricalChannel}, daq::RedPitayaDAQ, seq::Sequence, stepduration) @@ -586,7 +634,7 @@ function prepareHSwitchedOffsets(offsetVector::Vector{ProtocolOffsetElectricalCh end end - # Compute switch timing, assumption step is held for df * numPeriodsPerPatch + # Compute switch timing deadTimes = map(x-> x.hbridge.deadTime, filter(x-> x.range == HBRIDGE, map(x->channel(daq, id(x)), offsetVector))) maxTime = maximum(map(ustrip, deadTimes)) numSwitchPeriods = Int64(ceil(maxTime/stepduration)) diff --git a/src/Protocols/MPSMeasurementProtocol.jl b/src/Protocols/MPSMeasurementProtocol.jl index ff971425..d0aa8763 100644 --- a/src/Protocols/MPSMeasurementProtocol.jl +++ b/src/Protocols/MPSMeasurementProtocol.jl @@ -97,23 +97,28 @@ function _init(protocol::MPSMeasurementProtocol) protocol.protocolMeasState = ProtocolMeasState() try - seq, perm, offsets, calibsize = prepareProtocolSequences(protocol.params.sequence, getDAQ(scanner(protocol)); numPeriodsPerPatch = protocol.params.dfPeriodsPerOffset) + seq, perm, offsets, calibsize, numPeriodsPerFrame = prepareProtocolSequences(protocol.params.sequence, getDAQ(scanner(protocol)); numPeriodsPerOffset = protocol.params.dfPeriodsPerOffset) # For each patch assign nothing if invalid or otherwise index in "proper" frame - temp = Vector{Union{Int64, Nothing}}(nothing, acqNumPeriodsPerFrame(seq)) + patchPerm = Vector{Union{Int64, Nothing}}(nothing, numPeriodsPerFrame) if !protocol.params.sortPatches - perm = filter(in(perm), 1:acqNumPeriodsPerFrame(seq)) + # perm is arranged in a way that the first offset dimension switches the fastest + # if the patches should be saved in the order they were measured, we need to sort perm + perm = sort(perm) end + + # patchPerm contains the "target" for every patch that is measured, nothing for discarded patches, different indizes for non-averaged patches, identical indizes for averaged-patches # Same target for all frames to be averaged part = protocol.params.averagePeriodsPerOffset ? protocol.params.dfPeriodsPerOffset : 1 for (i, patches) in enumerate(Iterators.partition(perm, part)) - temp[patches] .= i + patchPerm[patches] .= i end protocol.sequence = seq - protocol.patchPermutation = temp + protocol.patchPermutation = patchPerm protocol.offsetfields = ustrip.(u"T", offsets) # TODO make robust protocol.calibsize = calibsize + @debug "Prepared Protocol Sequence: $(length(patchPerm)) measured and $(length(perm)) valid patches in Permutation" catch e throw(e) end @@ -279,7 +284,6 @@ function SequenceMeasState(protocol::MPSMeasurementProtocol) # Prepare buffering structures: # RedPitaya-> MPSBuffer -> Splitter --> FrameBuffer{Mmap} # |-> DriveFieldBuffer - @debug "Allocating buffer for $numFrames frames" numValidPatches = length(filter(x->!isnothing(x), protocol.patchPermutation)) averages = protocol.params.averagePeriodsPerOffset ? protocol.params.dfPeriodsPerOffset : 1 numFrames = div(numValidPatches, averages) @@ -405,10 +409,12 @@ function handleEvent(protocol::MPSMeasurementProtocol, event::DatasetStoreStorag data = reshape(data, rxNumSamplingPoints(sequence), length(rxChannels(sequence)), :, protocol.params.fgFrames + protocol.params.bgFrames * protocol.params.measureBackground) acqNumFrames(sequence, size(data, 4)) + periodsPerOffset = size(protocol.patchPermutation,1)÷size(protocol.offsetfields,1) + offsetPerm = zeros(Int64, size(data, 3)) for (index, patch) in enumerate(protocol.patchPermutation) if !isnothing(patch) - offsetPerm[patch] = index + offsetPerm[patch] = index÷periodsPerOffset end end offsets = protocol.offsetfields[offsetPerm, :] diff --git a/src/Protocols/Storage/ChainableBuffer.jl b/src/Protocols/Storage/ChainableBuffer.jl index f27322a3..9d775fbc 100644 --- a/src/Protocols/Storage/ChainableBuffer.jl +++ b/src/Protocols/Storage/ChainableBuffer.jl @@ -58,6 +58,7 @@ function FrameBuffer(sequence::Sequence) rxNumSamplingPoints = rxNumSamplesPerPeriod(sequence) numPeriods = acqNumPeriodsPerFrame(sequence) numChannel = length(rxChannels(sequence)) + @debug "Creating FrameBuffer with size $rxNumSamplingPoints x $numChannel x $numPeriods x $numFrames" buffer = zeros(Float32, rxNumSamplingPoints, numChannel, numPeriods, numFrames) return FrameBuffer(1, buffer) end @@ -69,6 +70,7 @@ function FrameBuffer(protocol::Protocol, file::String, sequence::Sequence) return FrameBuffer(protocol, file, Float32, (rxNumSamplingPoints, numChannel, numPeriods, numFrames)) end function FrameBuffer(protocol::Protocol, file::String, args...) + @debug "Creating memory-mapped FrameBuffer with size $(args[2])" mapped = mmap!(protocol, file, args...) return FrameBuffer(1, mapped) end From d75f7e43e0af4b33dc7bed1e1a23150a65fd2279 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Wed, 10 Apr 2024 08:56:21 +0200 Subject: [PATCH 089/168] fix devices to include config file parameter --- src/Devices/Device.jl | 20 ++++++----- .../SimpleBoreCollisionModule.jl | 11 +------ test/Scanner/ExtensibleScannerTest.jl | 11 +------ test/Scanner/ScannerConstructionTest.jl | 1 + .../Scanner.toml | 18 ++++++++++ test/TestDevices.jl | 33 +++++++++++++++++++ 6 files changed, 66 insertions(+), 28 deletions(-) create mode 100644 test/TestConfigs/TestBrokenDeviceMissingConfigFile/Scanner.toml diff --git a/src/Devices/Device.jl b/src/Devices/Device.jl index 5a539a5a..635c6c0b 100644 --- a/src/Devices/Device.jl +++ b/src/Devices/Device.jl @@ -13,7 +13,7 @@ abstract type DeviceParams end Base.close(device::Device) = @warn "The device type `$(typeof(device))` has no `close` function defined." function validateDeviceStruct(device::Type{<:Device}) - requiredFields = [:deviceID, :params, :optional, :present, :dependencies] + requiredFields = [:deviceID, :params, :optional, :present, :dependencies, :configFile] missingFields = [x for x in requiredFields if !in(x, fieldnames(device))] if length(missingFields) > 0 msg = "Device struct $(string(device)) is missing the required fields " * join(missingFields, ", ", " and ") @@ -33,8 +33,8 @@ macro add_device_fields(paramType) present::Bool = false #"Vector of dependencies for this device." dependencies::Dict{String,Union{Device,Missing}} - #"Path of the config used to load the device (either Scanner.toml or Device.toml)" - configFile::String + #"Path of the config used to create the device (either Scanner.toml or Device.toml)" + configFile::Union{String, Nothing} = nothing end) end @@ -53,15 +53,19 @@ isPresent(device::Device) = device.present "Retrieve the dependencies of a device." dependencies(device::Device) = device.dependencies -"Retrieve the configuration file of a device." +"Retrieve the path to the configuration file of a device." configFile(device::Device) = device.configFile -"Retrieve the configuration directory of the scanner." +"Retrieve the configuration directory of the scanner that the device is contructed from." function configDir(device::Device) - if basename(device.configFile) == "Scanner.toml" - return dirname(device.configFile) # if the device is read from a Scanner.toml the directory is the scanner directory + if !isnothing(device.configFile) + if basename(device.configFile) == "Scanner.toml" + return dirname(device.configFile) # if the device is read from a Scanner.toml the directory is the scanner directory + else + return joinpath(splitpath(device.configFile)[1:end-2]...) # otherwise the Device.toml is in the Devices folder + end else - return joinpath(splitpath(device.configFile)[1:end-2]...) # otherwise the Device.toml is in the Devices folder + return nothing end end diff --git a/src/Devices/Robots/CollisionModule/SimpleBoreCollisionModule.jl b/src/Devices/Robots/CollisionModule/SimpleBoreCollisionModule.jl index eb6b1b1d..4202a16b 100644 --- a/src/Devices/Robots/CollisionModule/SimpleBoreCollisionModule.jl +++ b/src/Devices/Robots/CollisionModule/SimpleBoreCollisionModule.jl @@ -15,16 +15,7 @@ end SimpleBoreCollisionModuleParams(dict::Dict) = params_from_dict(SimpleBoreCollisionModuleParams, dict) Base.@kwdef mutable struct SimpleBoreCollisionModule <: AbstractCollisionModule3D - "Unique device ID for this device as defined in the configuration." - deviceID::String - "Parameter struct for this devices read from the configuration." - params::SimpleBoreCollisionModuleParams - "Flag if the device is optional." - optional::Bool = false - "Flag if the device is present." - present::Bool = false - "Vector of dependencies for this device." - dependencies::Dict{String,Union{Device,Missing}} + @add_device_fields SimpleBoreCollisionModuleParams end collisionModuleType(cm::SimpleBoreCollisionModule) = PositionCollisionType() diff --git a/test/Scanner/ExtensibleScannerTest.jl b/test/Scanner/ExtensibleScannerTest.jl index 0ff1b967..51e4e401 100644 --- a/test/Scanner/ExtensibleScannerTest.jl +++ b/test/Scanner/ExtensibleScannerTest.jl @@ -16,16 +16,7 @@ MPIMeasurements, but also from the outside. FlexibleDAQParams(dict::Dict) = params_from_dict(FlexibleDAQParams, dict) Base.@kwdef mutable struct FlexibleDAQ <: MPIMeasurements.AbstractDAQ - "Unique device ID for this device as defined in the configuration." - deviceID::String - "Parameter struct for this devices read from the configuration." - params::FlexibleDAQParams - "Flag if the device is optional." - optional::Bool = false - "Flag if the device is present." - present::Bool = false - "Vector of dependencies for this device." - dependencies::Dict{String, Union{Device, Missing}} + MPIMeasurements.@add_device_fields FlexibleDAQParams end function MPIMeasurements.init(daq::FlexibleDAQ) diff --git a/test/Scanner/ScannerConstructionTest.jl b/test/Scanner/ScannerConstructionTest.jl index 33b68f0c..5e2937c1 100644 --- a/test/Scanner/ScannerConstructionTest.jl +++ b/test/Scanner/ScannerConstructionTest.jl @@ -6,6 +6,7 @@ @test_throws MPIMeasurements.ScannerConfigurationError MPIScanner("TestBrokenDeviceMissingOptional") @test_throws MPIMeasurements.ScannerConfigurationError MPIScanner("TestBrokenDeviceMissingPresent") @test_throws MPIMeasurements.ScannerConfigurationError MPIScanner("TestBrokenDeviceMissingParams") + @test_throws MPIMeasurements.ScannerConfigurationError MPIScanner("TestBrokenDeviceMissingConfigFile") end # Dependencies are checked correctly diff --git a/test/TestConfigs/TestBrokenDeviceMissingConfigFile/Scanner.toml b/test/TestConfigs/TestBrokenDeviceMissingConfigFile/Scanner.toml new file mode 100644 index 00000000..97a3578f --- /dev/null +++ b/test/TestConfigs/TestBrokenDeviceMissingConfigFile/Scanner.toml @@ -0,0 +1,18 @@ +[General] +boreSize = "1337mm" +facility = "My awesome institute" +manufacturer = "Me, Myself and I" +name = "TestBrokenDeviceMissingConfigFile" +topology = "FFL" +gradient = "42T/m" +datasetStore = "./tmp/TestSimpleSimulatedScannerStore" +defaultSequence = "SimpleSimulatedSequence" + +[Devices] +initializationOrder = [ + "testDevice", +] + +[Devices.testDevice] +deviceType = "TestMissingConfigFileDevice" +dependencies = [] diff --git a/test/TestDevices.jl b/test/TestDevices.jl index e22cefb9..b6533c32 100644 --- a/test/TestDevices.jl +++ b/test/TestDevices.jl @@ -46,6 +46,8 @@ Base.@kwdef mutable struct TestDevice <: Device present::Bool = false "Vector of dependencies for this device." dependencies::Dict{String, Union{Device, Missing}} + "Path of the config used to create the device (either Scanner.toml or Device.toml)" + configFile::Union{String, Nothing} = nothing initRan::Bool = false end @@ -73,6 +75,8 @@ Base.@kwdef mutable struct TestDependencyDevice <: Device present::Bool = false "Vector of dependencies for this device." dependencies::Dict{String, Union{Device, Missing}} + "Path of the config used to create the device (either Scanner.toml or Device.toml)" + configFile::Union{String, Nothing} = nothing initRan::Bool = false end @@ -100,6 +104,8 @@ Base.@kwdef mutable struct TestDeviceB <: Device present::Bool = false "Vector of dependencies for this device." dependencies::Dict{String, Union{Device, Missing}} + "Path of the config used to create the device (either Scanner.toml or Device.toml)" + configFile::Union{String, Nothing} = nothing initRan::Bool = false end @@ -123,6 +129,8 @@ Base.@kwdef mutable struct TestMissingIDDevice <: Device present::Bool = false "Vector of dependencies for this device." dependencies::Dict{String, Union{Device, Missing}} + "Path of the config used to create the device (either Scanner.toml or Device.toml)" + configFile::Union{String, Nothing} = nothing initRan::Bool = false end @@ -138,6 +146,8 @@ Base.@kwdef mutable struct TestMissingParamsDevice <: Device present::Bool = false "Vector of dependencies for this device." dependencies::Dict{String, Union{Device, Missing}} + "Path of the config used to create the device (either Scanner.toml or Device.toml)" + configFile::Union{String, Nothing} = nothing initRan::Bool = false end @@ -153,6 +163,8 @@ Base.@kwdef mutable struct TestMissingOptionalDevice <: Device present::Bool = false "Vector of dependencies for this device." dependencies::Dict{String, Union{Device, Missing}} + "Path of the config used to create the device (either Scanner.toml or Device.toml)" + configFile::Union{String, Nothing} = nothing initRan::Bool = false end @@ -168,6 +180,8 @@ Base.@kwdef mutable struct TestMissingPresentDevice <: Device #present::Bool = false "Vector of dependencies for this device." dependencies::Dict{String, Union{Device, Missing}} + "Path of the config used to create the device (either Scanner.toml or Device.toml)" + configFile::Union{String, Nothing} = nothing initRan::Bool = false end @@ -183,6 +197,25 @@ Base.@kwdef mutable struct TestMissingDependencyDevice <: Device present::Bool = false #"Vector of dependencies for this device." #dependencies::Dict{String, Union{Device, Missing}} + "Path of the config used to create the device (either Scanner.toml or Device.toml)" + configFile::Union{String, Nothing} = nothing + + initRan::Bool = false +end + +Base.@kwdef mutable struct TestMissingConfigFileDevice <: Device + "Unique device ID for this device as defined in the configuration." + deviceID::String + "Parameter struct for this devices read from the configuration." + params::TestDeviceParams + "Flag if the device is optional." + optional::Bool = false + "Flag if the device is present." + present::Bool = false + "Vector of dependencies for this device." + dependencies::Dict{String, Union{Device, Missing}} + #"Path of the config used to create the device (either Scanner.toml or Device.toml)" + #configFile::Union{String, Nothing} = nothing initRan::Bool = false end \ No newline at end of file From 30cba37fb8529204e11f1dedb9ad3659ddff1f44 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Wed, 10 Apr 2024 10:19:51 +0200 Subject: [PATCH 090/168] fix floating point error --- src/Sequences/Sequence.jl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Sequences/Sequence.jl b/src/Sequences/Sequence.jl index 32caf463..d67e54fc 100644 --- a/src/Sequences/Sequence.jl +++ b/src/Sequences/Sequence.jl @@ -476,11 +476,12 @@ rxNumChannels(sequence::Sequence) = length(rxChannels(sequence)) export rxNumSamplingPoints function rxNumSamplingPoints(sequence::Sequence) result = upreferred(rxSamplingRate(sequence)*dfCycle(sequence)) - if !isinteger(result) + if !(result≈round(result)) + @debug "rxNumSamplingPoints" result≈round(result) rxSamplingRate(sequence) dfCycle(sequence) throw(ScannerConfigurationError("The selected combination of divider and decimation results in non-integer sampling points.")) end - return Int64(result) + return round(Int64,result) end export rxNumSamplesPerPeriod From 47983faa7bdd9a31bd6ff6d8b21098caf8f71f57 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Wed, 10 Apr 2024 10:21:21 +0200 Subject: [PATCH 091/168] simplify chan and comp creation --- src/Sequences/PeriodicElectricalChannel.jl | 7 +++---- src/Sequences/Sequence.jl | 7 +++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Sequences/PeriodicElectricalChannel.jl b/src/Sequences/PeriodicElectricalChannel.jl index 3ec5f1a3..c0072b02 100644 --- a/src/Sequences/PeriodicElectricalChannel.jl +++ b/src/Sequences/PeriodicElectricalChannel.jl @@ -128,10 +128,9 @@ end function createChannelComponent(componentID::AbstractString, componentDict::Dict{String, Any}) if haskey(componentDict, "type") type = pop!(componentDict, "type") - knownComponents = MPIFiles.concreteSubtypes(ElectricalComponent) - index = findfirst(x -> x == type, string.(knownComponents)) - if !isnothing(index) - createChannelComponent(componentID, knownComponents[index], componentDict) + concreteType = getConcreteType(ElectricalComponent, type) + if !isnothing(concreteType) + createChannelComponent(componentID, concreteType, componentDict) else error("Component $componentID has an unknown channel type `$type`.") end diff --git a/src/Sequences/Sequence.jl b/src/Sequences/Sequence.jl index d67e54fc..bb1174fa 100644 --- a/src/Sequences/Sequence.jl +++ b/src/Sequences/Sequence.jl @@ -179,10 +179,9 @@ end function createFieldChannel(channelID::AbstractString, channelDict::Dict{String, Any}) if haskey(channelDict, "type") type = pop!(channelDict, "type") - knownChannels = MPIFiles.concreteSubtypes(TxChannel) - index = findfirst(x -> x == type, string.(knownChannels)) - if !isnothing(index) - createFieldChannel(channelID, knownChannels[index], channelDict) + concreteType = getConcreteType(TxChannel, type) + if !isnothing(concreteType) + createFieldChannel(channelID, concreteType, channelDict) else error("Channel $channelID has an unknown channel type `$type`.") end From e16c76ab157539d93c8ddf38c1db833c674a2764 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Wed, 10 Apr 2024 10:22:13 +0200 Subject: [PATCH 092/168] parse divider in .toml --- src/Sequences/PeriodicElectricalChannel.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Sequences/PeriodicElectricalChannel.jl b/src/Sequences/PeriodicElectricalChannel.jl index c0072b02..ba9efa9e 100644 --- a/src/Sequences/PeriodicElectricalChannel.jl +++ b/src/Sequences/PeriodicElectricalChannel.jl @@ -175,7 +175,7 @@ function createChannelComponent(componentID::AbstractString, ::Type{ArbitraryEle end function extractBasicComponentProperties(componentDict::Dict{String, Any}) - divider = componentDict["divider"] + divider = parse(Int, string(componentDict["divider"])) amplitude = uparse.(componentDict["amplitude"]) if eltype(amplitude) <: Unitful.Current amplitude = amplitude .|> u"A" From 5a3dd0be6edcdbcc9868efd382bf68a8ce20d99d Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Wed, 10 Apr 2024 10:22:50 +0200 Subject: [PATCH 093/168] fix frame averages --- src/Protocols/Storage/ProducerConsumer.jl | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/Protocols/Storage/ProducerConsumer.jl b/src/Protocols/Storage/ProducerConsumer.jl index 572e200a..70e9f335 100644 --- a/src/Protocols/Storage/ProducerConsumer.jl +++ b/src/Protocols/Storage/ProducerConsumer.jl @@ -1,16 +1,17 @@ -SequenceMeasState(x, sequence::ControlSequence, sequenceBuffer::Nothing = nothing) = SequenceMeasState(x, sequence, StorageBuffer[]) -function SequenceMeasState(x, sequence::ControlSequence, sequenceBuffer::Vector{StorageBuffer}) +SequenceMeasState(daq::RedPitayaDAQ, sequence::ControlSequence, sequenceBuffer::Nothing = nothing) = SequenceMeasState(daq, sequence, StorageBuffer[]) +function SequenceMeasState(daq::RedPitayaDAQ, sequence::ControlSequence, sequenceBuffer::Vector{StorageBuffer}) numFrames = acqNumFrames(sequence.targetSequence) numPeriods = acqNumPeriodsPerFrame(sequence.targetSequence) bufferShape = controlMatrixShape(sequence) buffer = DriveFieldBuffer(1, zeros(ComplexF64, bufferShape[1], bufferShape[2], numPeriods, numFrames), sequence) - avgFrames = acqNumFrameAverages(sequence.targetSequence) - if avgFrames > 1 - samples = rxNumSamplesPerPeriod(sequence.targetSequence) - periods = acqNumPeriodsPerFrame(sequence.targetSequence) - buffer = AverageBuffer(buffer, samples, len, periods, avgFrames) - end - return SequenceMeasState(x, sequence.targetSequence, push!(sequenceBuffer, buffer)) + frameAvgs = acqNumFrameAverages(sequence.targetSequence) + if frameAvgs > 1 + samples = rxNumSamplesPerPeriod(sequence.targetSequence) + @info daq.refChanIDs length(daq.refChanIDs) + #TODO/JA: replace hardcoded 3 + buffer = AverageBuffer(buffer, samples, 3, numPeriods, frameAvgs) + end + return SequenceMeasState(daq, sequence.targetSequence, push!(sequenceBuffer, buffer)) end SequenceMeasState(protocol::Protocol, x, sequenceBuffer::Union{Nothing, Vector{StorageBuffer}} = nothing) = SequenceMeasState(getDAQ(scanner(protocol)), x, sequenceBuffer) function SequenceMeasState(daq::RedPitayaDAQ, sequence::Sequence, sequenceBuffer::Union{Nothing, Vector{StorageBuffer}} = nothing) From 3e451fa5bd051e7d47e772bce4dccc1a105334b6 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Wed, 10 Apr 2024 10:24:36 +0200 Subject: [PATCH 094/168] add DC calibration and debugging --- src/Devices/DAQ/RedPitayaDAQ.jl | 1 + src/Devices/Virtual/TxDAQController.jl | 35 +++++++++++++++++++------- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index 4154f1f3..c15c7504 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -275,6 +275,7 @@ function setSequenceParams(daq::RedPitayaDAQ, luts::Vector{Union{Nothing, Array{ @info "Set sequence params" stepsPerRepetition = div(periodsPerFrame(daq.rpc), daq.acqPeriodsPerPatch) + @debug "Set sequence params" samplesPerStep=div(samplesPerPeriod(daq.rpc) * periodsPerFrame(daq.rpc), stepsPerRepetition) result = execute!(daq.rpc) do batch @add_batch batch samplesPerStep!(daq.rpc, div(samplesPerPeriod(daq.rpc) * periodsPerFrame(daq.rpc), stepsPerRepetition)) @add_batch batch clearSequence!(daq.rpc) diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index 664a0202..5236ecbc 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -35,6 +35,7 @@ mutable struct AWControlSequence <: ControlSequence refIndices::Vector{Int64} rfftIndices::BitArray{3} # Matrix of size length(controlledChannelsDict) x 4 (max. num of components) x len(rfft) dcCorrection::Union{Vector{Float64}, Nothing} + dcCalibration::Union{Vector{Float64}, Nothing} end @enum ControlResult UNCHANGED UPDATED INVALID @@ -110,7 +111,7 @@ function ControlSequence(txCont::TxDAQController, target::Sequence, daq::Abstrac rfftIndices = createRFFTindices(controlledChannelsDict, target, daq) - return AWControlSequence(target, currSeq, controlledChannelsDict, refIndices, rfftIndices, nothing) + return AWControlSequence(target, currSeq, controlledChannelsDict, refIndices, rfftIndices, nothing, nothing) end end @@ -393,6 +394,8 @@ function controlTx(txCont::TxDAQController, control::ControlSequence) error("DC correction too large! Wanted to set $(dc_correction)") end control.dcCorrection = dc_correction + control.dcCalibration = (ref_offsets_δ.-ref_offsets_0)./δ + @info "Found DC Calibration" control.dcCalibration end while !controlPhaseDone && i <= txCont.params.maxControlSteps @@ -433,7 +436,11 @@ function controlTx(txCont::TxDAQController, control::ControlSequence) @info "Control measurement finished" @info "Evaluating control step" - Γ = read(sink(buffer, DriveFieldBuffer))[:, :, 1, end] # calcFieldsFromRef happened here already + tmp = read(sink(buffer, DriveFieldBuffer)) + @debug "Size of calc fields from ref" size(tmp) + # TODO: Fix this to ensure, that the number of frames is alwys large enough (maybe add a parameter riseup time) + + Γ = mean(tmp[:, :, 1, 1:end],dims=3)[:,:,1] # calcFieldsFromRef happened here already if !isnothing(Γ) controlPhaseDone = controlStep!(control, txCont, Γ, Ω) == UNCHANGED if controlPhaseDone @@ -584,7 +591,7 @@ function checkFieldDeviation(cont::ControlSequence, txCont::TxDAQController, Γ: Ωt = checkVoltLimits(Ω,cont,return_time_signal=true)' diff = (Ωt .- Γt) - @debug "checkFieldDeviation" diff=lineplot(1:rxNumSamplingPoints(cont.currSequence),diff) + @debug "checkFieldDeviation" diff=lineplot(1:rxNumSamplingPoints(cont.currSequence),diff, canvas=DotCanvas, border=:ascii) @debug "checkFieldDeviation" max_diff = maximum(abs.(diff)) end @debug "Check field deviation [T]" Ω Γ @@ -600,10 +607,12 @@ function checkFieldDeviation(cont::AWControlSequence, txCont::TxDAQController, Γt = checkVoltLimits(Γ,cont,return_time_signal=true)' Ωt = checkVoltLimits(Ω,cont,return_time_signal=true)' - + @debug "checkFieldDeviation" Γ[allComponentMask(cont)]' abs.(Γ[allComponentMask(cont)])' angle.(Γ[allComponentMask(cont)])'# abs.(Ω[allComponentMask(cont)])' angle.(Ω[allComponentMask(cont)])' diff = (Ωt .- Γt) - @debug "checkFieldDeviation" diff=lineplot(1:rxNumSamplingPoints(cont.currSequence),diff) - @debug "checkFieldDeviation" max_diff = maximum(abs.(diff)) + diff = diff .- mean(diff, dims=1) + @debug "checkFieldDeviation" diff=lineplot(1:rxNumSamplingPoints(cont.currSequence),diff, canvas=DotCanvas, border=:ascii) + @info "Observed field deviation:\nmax_diff:\t$(maximum(abs.(diff))*1000) mT" + diff = diff .- mean(diff, dims=1) amplitude_ok = abs.(diff).< ustrip(u"T", txCont.params.absoluteAmplitudeAccuracy) return all(amplitude_ok) end @@ -646,6 +655,13 @@ function updateControlMatrix(cont::AWControlSequence, txCont::TxDAQController, # The problem is, that to achieve 0 we will always output zero, but we would need a much more sophisticated method to solve this newTx = κ./Γ.*Ω + # handle DC separately: + if txCont.params.findZeroDC + @info "update ControlMatrix" κ[:,1] newTx[:,1] + @info "2" Γ[:,1] (κ[:,1].+(Ω[:,1].-Γ[:,1])./cont.dcCalibration)-cont.dcCorrection + newTx[:,1] .= (κ[:,1].+(Ω[:,1].-Γ[:,1])./cont.dcCalibration)-cont.dcCorrection + end + #@debug "Last TX matrix [V]:" κ=lineplot(1:rxNumSamplingPoints(cont.currSequence),checkVoltLimits(κ,cont,return_time_signal=true)') #@debug "Ref matrix [T]:" Γ=lineplot(1:rxNumSamplingPoints(cont.currSequence),checkVoltLimits(Γ,cont,return_time_signal=true)') #@debug "Desired matrix [V]:" Ω=lineplot(1:rxNumSamplingPoints(cont.currSequence),checkVoltLimits(Ω,cont,return_time_signal=true)') @@ -823,6 +839,7 @@ function updateControlSequence!(cont::AWControlSequence, newTx::Matrix) for (i, channel) in enumerate(periodicElectricalTxChannels(cont.currSequence)) if cont.rfftIndices[i,end,1] if !isnothing(cont.dcCorrection) + @debug "updateControlSequence" (cont.dcCorrection[i]+real(newTx[i,1]))*1.0u"V" offset!(channel, (cont.dcCorrection[i]+real(newTx[i,1]))*1.0u"V") else offset!(channel, real(newTx[i,1])*1.0u"V") @@ -855,7 +872,7 @@ function checkFieldToVolt(oldTx::Matrix{<:Complex}, Γ::Matrix{<:Complex}, cont: calibFieldToVoltEstimate = [ustrip(u"V/T", chan.calibration(frequencies[i])) for (i,chan) in enumerate(getControlledDAQChannels(cont))] calibFieldToVoltMeasured = (diag(oldTx) ./ diag(Γ)) - abs_deviation = abs.(1.0 .- calibFieldToVoltMeasured./calibFieldToVoltEstimate) + abs_deviation = abs.(1.0 .- abs.(calibFieldToVoltMeasured)./abs.(calibFieldToVoltEstimate)) phase_deviation = angle.(calibFieldToVoltMeasured./calibFieldToVoltEstimate) @debug "checkFieldToVolt: We expected $(calibFieldToVoltEstimate) V/T and got $(calibFieldToVoltMeasured) V/T, deviation: $(abs_deviation*100) %" valid = maximum( abs_deviation ) < txCont.params.fieldToVoltDeviation @@ -896,7 +913,7 @@ function checkVoltLimits(newTx::Matrix{<:Complex}, cont::CrossCouplingControlSeq end valid = all(validChannel) if !valid - @debug "Valid Tx Channel" validChannel + @debug "Valid Tx Channel" validChannel newTx @warn "New control sequence exceeds voltage limits of tx channel" end return valid @@ -917,7 +934,7 @@ function checkVoltLimits(newTx::Matrix{<:Complex}, cont::AWControlSequence; retu validPeak = maximum(abs.(testSignalTime), dims=2) .< ustrip.(u"V", getproperty.(getControlledDAQChannels(cont),:limitPeak)) valid = all(validSlew) && all(validPeak) - @debug "Check Volt Limit" p=lineplot(1:N,testSignalTime') maximum(abs.(testSignalTime), dims=2) maximum(abs.(slew_rate), dims=2) + @debug "Check Volt Limit" p=lineplot(1:N,testSignalTime', canvas=DotCanvas, border=:ascii) maximum(abs.(testSignalTime), dims=2) maximum(abs.(slew_rate), dims=2) if !valid @debug "Valid Tx Channel" validSlew validPeak @warn "New control sequence exceeds voltage limits (slew rate or peak) of tx channel" From 97b4472d6f24000926ecddf25ac0d82ebbb22b5f Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Wed, 10 Apr 2024 10:24:58 +0200 Subject: [PATCH 095/168] fix imports and dependencies --- Project.toml | 8 ++++---- src/MPIMeasurements.jl | 3 +++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Project.toml b/Project.toml index 4febca19..aa10472b 100644 --- a/Project.toml +++ b/Project.toml @@ -1,12 +1,13 @@ name = "MPIMeasurements" uuid = "a874a27a-e9a7-503d-98b6-bf61df4bb725" authors = ["Tobias Knopp "] -version = "0.4.3" +version = "0.6.0" [deps] DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" +FFTW = "7a1cc6ca-52ef-59f5-83cd-3a7055c09341" HDF5 = "f67ccb44-e63f-5c2f-98bd-6dc0ccc4ba2f" InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" LibSerialPort = "a05a14c7-6e3b-5ba9-90a2-45558833e1df" @@ -17,10 +18,10 @@ Mmap = "a63ad114-7e13-5084-954f-fe012c677804" ProgressMeter = "92933f4c-e287-5a05-a399-4b506db050ca" REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" RedPitayaDAQServer = "c544963a-496b-56d4-a5fe-f99a3f174c8f" -Reexport = "189a3867-3050-52da-a836-e630ba90ab69" ReplMaker = "b873ce64-0db9-51f5-a568-4457d8e49576" Scratch = "6c6a2e73-6563-6170-7368-637461726353" Sockets = "6462fe0b-24de-5631-8697-dd941f90decc" +Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" StringEncodings = "69024149-9ee7-55f6-a4c4-859efe599b68" TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76" ThreadPools = "b189fb0b-2eb5-4ed4-bc0c-d34c51242431" @@ -37,7 +38,7 @@ HDF5 = "0.16" InteractiveUtils = "1" LibSerialPort = "0.5.2" LinearAlgebra = "1" -MPIFiles = "0.12, 0.13, 0.14" +MPIFiles = "0.15" MacroTools = "0.5.6" Mmap = "1" Pkg = "1" @@ -45,7 +46,6 @@ Plots = "1.39" ProgressMeter = "1.5" REPL = "1" RedPitayaDAQServer = "0.6, 0.7" -Reexport = "0.2, 1.0" ReplMaker = "0.2.7" Scratch = "1.1.0" Sockets = "1" diff --git a/src/MPIMeasurements.jl b/src/MPIMeasurements.jl index 56678aaf..942e58c0 100644 --- a/src/MPIMeasurements.jl +++ b/src/MPIMeasurements.jl @@ -15,11 +15,14 @@ using InteractiveUtils using Mmap using Scratch using StringEncodings +using Statistics: mean using DocStringExtensions using MacroTools using LibSerialPort using UnicodePlots using LinearAlgebra +using HDF5 +using FFTW using ReplMaker import REPL From ae57c0a54452654378ee4f0af3648a1c029e78d8 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Wed, 10 Apr 2024 13:41:08 +0200 Subject: [PATCH 096/168] allow deepsubtypes on Type, subtypes is also defined on Type --- src/Scanner.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Scanner.jl b/src/Scanner.jl index 58413655..bae5fdcb 100644 --- a/src/Scanner.jl +++ b/src/Scanner.jl @@ -11,7 +11,7 @@ export MPIScanner, MPIScannerGeneral, scannerBoreSize, scannerFacility, Recursively find all concrete types of the given type. """ -function deepsubtypes(type::DataType) +function deepsubtypes(type::Type) subtypes_ = subtypes(type) allSubtypes = subtypes_ for subtype in subtypes_ @@ -26,7 +26,7 @@ end Retrieve the concrete type of a given supertype corresponding to a given string. """ -function getConcreteType(supertype_::DataType, type::String) +function getConcreteType(supertype_::Type, type::String) knownTypes = deepsubtypes(supertype_) foundImplementation = nothing for Implementation in knownTypes From 14a81c7cda7cb1c1e061f77b3a51362e5c21db3e Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Fri, 12 Apr 2024 11:21:06 +0200 Subject: [PATCH 097/168] fix merge errors --- src/Devices/DAQ/RedPitayaDAQ.jl | 18 +++++++++--------- src/Devices/Virtual/TxDAQController.jl | 2 +- .../MultiSequenceSystemMatrixProtocol.jl | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index b19d2363..35a31cba 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -32,7 +32,7 @@ end function createDAQChannel(::Type{RedPitayaLUTChannelParams}, dict::Dict{String, Any}) splattingDict = Dict{Symbol, Any}() - splattingDict[:channelIdx] = value["channel"] + splattingDict[:channelIdx] = dict["channel"] if haskey(dict, "calibration") calib = uparse.(dict["calibration"]) @@ -46,20 +46,20 @@ function createDAQChannel(::Type{RedPitayaLUTChannelParams}, dict::Dict{String, splattingDict[:calibration] = calib end - if haskey(value, "hbridge") - splattingDict[:hbridge] = createDAQChannels(DAQHBridge, value["hbridge"]) + if haskey(dict, "hbridge") + splattingDict[:hbridge] = createDAQChannels(DAQHBridge, dict["hbridge"]) end - if haskey(value, "range") - splattingDict[:range] = value["range"] + if haskey(dict, "range") + splattingDict[:range] = dict["range"] end - if haskey(value, "switchTime") - splattingDict[:switchTime] = uparse(value["switchTime"]) + if haskey(dict, "switchTime") + splattingDict[:switchTime] = uparse(dict["switchTime"]) end - if haskey(value, "switchEnable") - splattingDict[:switchEnable] = value["switchEnable"] + if haskey(dict, "switchEnable") + splattingDict[:switchEnable] = dict["switchEnable"] end return RedPitayaLUTChannelParams(; splattingDict...) end diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index 5236ecbc..5997ef27 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -300,7 +300,7 @@ end function measureMeanReferenceSignal(daq::AbstractDAQ, seq::Sequence) channel = Channel{channelType(daq)}(32) - buf = AsyncBuffer(SimpleFrameBuffer(1, zeros(Float32,rxNumSamplesPerPeriod(seq),length(daq.rpv)*2,acqNumPeriodsPerFrame(seq),acqNumFrames(seq))), daq) + buf = AsyncBuffer(FrameBuffer(1, zeros(Float32,rxNumSamplesPerPeriod(seq),length(daq.rpv)*2,acqNumPeriodsPerFrame(seq),acqNumFrames(seq))), daq) @info "DC Control measurement started" producer = @async begin @debug "Starting DC control producer" diff --git a/src/Protocols/MultiSequenceSystemMatrixProtocol.jl b/src/Protocols/MultiSequenceSystemMatrixProtocol.jl index a9df935d..2e273f06 100644 --- a/src/Protocols/MultiSequenceSystemMatrixProtocol.jl +++ b/src/Protocols/MultiSequenceSystemMatrixProtocol.jl @@ -301,7 +301,7 @@ function asyncConsumer(channel::Channel, protocol::MultiSequenceSystemMatrixProt end sinks = StorageBuffer[] - push!(sinks, SimpleFrameBuffer(1, view(calib.signals, :, :, :, startIdx:stopIdx))) + push!(sinks, FrameBuffer(1, view(calib.signals, :, :, :, startIdx:stopIdx))) #sequence = protocol.params.sequence #if protocol.params.controlTx # sequence = protocol.contSequence From d884c7a07dcd314ab38611fdb961b5774fdadc9a Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Fri, 12 Apr 2024 11:21:45 +0200 Subject: [PATCH 098/168] improve error messages --- src/Devices/DAQ/DAQ.jl | 5 +++++ src/Scanner.jl | 7 ++++++- src/Sequences/Sequence.jl | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Devices/DAQ/DAQ.jl b/src/Devices/DAQ/DAQ.jl index b94b169e..99aa2332 100644 --- a/src/Devices/DAQ/DAQ.jl +++ b/src/Devices/DAQ/DAQ.jl @@ -284,6 +284,7 @@ function applyForwardCalibration!(seq::Sequence, daq::AbstractDAQ) for channel in periodicElectricalTxChannels(seq) off = offset(channel) if dimension(off) != dimension(1.0u"V") + isnothing(calibration(daq, id(channel))) && throw(ScannerConfigurationError("An offset value in channel $(id(channel)) requires calibration but no calibration is configured on the DAQ channel!")) offsetVolts = off*abs(calibration(daq, id(channel))(0)) # use DC value for offsets offset!(channel, uconvert(u"V",offsetVolts)) end @@ -292,6 +293,7 @@ function applyForwardCalibration!(seq::Sequence, daq::AbstractDAQ) amp = amplitude(comp) pha = phase(comp) if dimension(amp) != dimension(1.0u"V") + isnothing(calibration(daq, id(channel))) && throw(ScannerConfigurationError("An amplitude value in channel $(id(channel)) requires calibration but no calibration is configured on the DAQ channel!")) f_comp = ustrip(u"Hz", txBaseFrequency(seq)) / divider(comp) complex_comp = (amp*exp(im*pha)) * calibration(daq, id(channel))(f_comp) amplitude!(comp, uconvert(u"V",abs(complex_comp))) @@ -310,6 +312,7 @@ function applyForwardCalibration!(seq::Sequence, daq::AbstractDAQ) if lutChannel isa StepwiseElectricalChannel values = lutChannel.values if dimension(values[1]) != dimension(1.0u"V") + isnothing(calibration(daq, id(lutChannel))) && throw(ScannerConfigurationError("A value in channel $(id(lutChannel)) requires calibration but no calibration is configured on the DAQ channel!")) values = values.*calibration(daq, id(lutChannel)) lutChannel.values = values end @@ -317,10 +320,12 @@ function applyForwardCalibration!(seq::Sequence, daq::AbstractDAQ) amp = lutChannel.amplitude off = lutChannel.offset if dimension(amp) != dimension(1.0u"V") + isnothing(calibration(daq, id(lutChannel))) && throw(ScannerConfigurationError("An amplitude value in channel $(id(lutChannel)) requires calibration but no calibration is configured on the DAQ channel!")) amp = amp*calibration(daq, id(lutChannel)) lutChannel.amplitude = amp end if dimension(off) != dimension(1.0u"V") + isnothing(calibration(daq, id(lutChannel))) && throw(ScannerConfigurationError("An offset value in channel $(id(lutChannel)) requires calibration but no calibration is configured on the DAQ channel!")) off = off*calibration(daq, id(lutChannel)) lutChannel.offset = off end diff --git a/src/Scanner.jl b/src/Scanner.jl index e1d2b54f..b3ad3bbc 100644 --- a/src/Scanner.jl +++ b/src/Scanner.jl @@ -129,19 +129,24 @@ function getFittingDeviceParamsType(params::Dict{String, Any}, deviceType::Strin fittingDeviceParams = [] lastException = nothing + lastBacktrace = nothing for (i, paramType) in enumerate(tempDeviceParams) try tempParams = paramType(copy(params)) push!(fittingDeviceParams, tempParams) catch ex lastException = ex + lastBacktrace = Base.catch_backtrace() end end if length(fittingDeviceParams) == 1 return fittingDeviceParams[1] elseif length(fittingDeviceParams) == 0 && !isnothing(lastException) - throw(lastException) + Base.printstyled("ERROR: "; color=:red, bold=true) + Base.showerror(stdout, lastException) + Base.show_backtrace(stdout, lastBacktrace) + throw("The above error occured during device creation!") else return nothing end diff --git a/src/Sequences/Sequence.jl b/src/Sequences/Sequence.jl index ff53c6c4..0249fbf6 100644 --- a/src/Sequences/Sequence.jl +++ b/src/Sequences/Sequence.jl @@ -486,7 +486,7 @@ function rxNumSamplingPoints(sequence::Sequence) result = upreferred(rxSamplingRate(sequence)*dfCycle(sequence)) if !(result≈round(result)) @debug "rxNumSamplingPoints" result≈round(result) rxSamplingRate(sequence) dfCycle(sequence) - throw(ScannerConfigurationError("The selected combination of divider and decimation results in non-integer sampling points.")) + throw(ScannerConfigurationError("The selected combination of divider ($(dfSamplesPerCycle(sequence))) and decimation ($(Int(baseFrequency(sequence)/rxSamplingRate(sequence)))) results in non-integer sampling points ($result).")) end return round(Int64,result) From dfd4f9538c2a1be57a75a2ae9e0bb2c9f37a1e65 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Wed, 22 Nov 2023 13:39:42 +0100 Subject: [PATCH 099/168] rational dividers (working FFL Sequence) --- src/Devices/Virtual/TxDAQController.jl | 7 ++++++- src/Protocols/Storage/MDF.jl | 2 +- src/Sequences/PeriodicElectricalChannel.jl | 13 +++++++------ src/Sequences/Sequence.jl | 8 ++++---- 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index f186851d..76e2e76a 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -641,6 +641,11 @@ function updateControlMatrix(cont::AWControlSequence, txCont::TxDAQController, # The problem is, that to achieve 0 we will always output zero, but we would need a much more sophisticated method to solve this newTx = κ./Γ.*Ω + # @debug "Last TX matrix [V]:" κ + # @debug "Ref matrix [T]:" Γ + # @debug "Desired matrix [T]:" Ω + # @debug "New TX matrix [V]:" newTx + #@debug "Last TX matrix [V]:" κ=lineplot(1:rxNumSamplingPoints(cont.currSequence),checkVoltLimits(κ,cont,return_time_signal=true)') #@debug "Ref matrix [T]:" Γ=lineplot(1:rxNumSamplingPoints(cont.currSequence),checkVoltLimits(Γ,cont,return_time_signal=true)') #@debug "Desired matrix [V]:" Ω=lineplot(1:rxNumSamplingPoints(cont.currSequence),checkVoltLimits(Ω,cont,return_time_signal=true)') @@ -703,7 +708,7 @@ end function calcFieldFromRef(cont::CrossCouplingControlSequence, uRef, ::SortedRef) len = numControlledChannels(cont) N = rxNumSamplingPoints(cont.currSequence) - dividers = Int64[divider.(getPrimaryComponents(cont))] + dividers = [divider.(getPrimaryComponents(cont))] frequencies = ustrip(u"Hz", txBaseFrequency(cont.currSequence)) ./ dividers Γ = zeros(ComplexF64, len, len) calcFieldFromRef!(Γ, cont, uRef, SortedRef()) diff --git a/src/Protocols/Storage/MDF.jl b/src/Protocols/Storage/MDF.jl index 97ee7066..8cf1424f 100644 --- a/src/Protocols/Storage/MDF.jl +++ b/src/Protocols/Storage/MDF.jl @@ -283,7 +283,7 @@ function fillMDFAcquisition(mdf::MDFv2InMemory, scanner::MPIScanner, sequence::S # /acquisition/drivefield/ subgroup MPIFiles.dfBaseFrequency(mdf, ustrip(u"Hz", dfBaseFrequency(sequence))) MPIFiles.dfCycle(mdf, ustrip(u"s", dfCycle(sequence))) - MPIFiles.dfDivider(mdf, dfDivider(sequence)) + MPIFiles.dfDivider(mdf, Float64.(dfDivider(sequence))) MPIFiles.dfNumChannels(mdf, dfNumChannels(sequence)) MPIFiles.dfPhase(mdf, ustrip.(u"rad", dfPhase(sequence))) MPIFiles.dfStrength(mdf, ustrip.(u"T", dfStrength(sequence))) diff --git a/src/Sequences/PeriodicElectricalChannel.jl b/src/Sequences/PeriodicElectricalChannel.jl index cbc6f50d..57cf80fe 100644 --- a/src/Sequences/PeriodicElectricalChannel.jl +++ b/src/Sequences/PeriodicElectricalChannel.jl @@ -6,7 +6,7 @@ export PeriodicElectricalComponent, SweepElectricalComponent, PeriodicElectrical Base.@kwdef mutable struct PeriodicElectricalComponent <: ElectricalComponent id::AbstractString "Divider of the component." - divider::Integer + divider::Rational "Amplitude (peak) of the component for each period of the field." amplitude::Union{Vector{typeof(1.0u"T")}, Vector{typeof(1.0u"V")}, Vector{typeof(1.0u"A")}} # Is it really the right choice to have the periods here? Or should it be moved to the MagneticField? "Phase of the component for each period of the field." @@ -30,7 +30,7 @@ end Base.@kwdef mutable struct ArbitraryElectricalComponent <: ElectricalComponent id::AbstractString "Divider of the component." - divider::Integer + divider::Rational "Amplitude scale of the base waveform for each period of the field" amplitude::Union{Vector{typeof(1.0u"T")}, Vector{typeof(1.0u"A")}, Vector{typeof(1.0u"V")}} "Phase of the component for each period of the field." @@ -138,9 +138,10 @@ function createChannelComponent(componentID::AbstractString, ::Type{ArbitraryEle return ArbitraryElectricalComponent(id=componentID, divider=divider,amplitude=amplitude, phase=phase, values=values) end - +Base.rationalize(x::Integer) = Rational(x) function extractBasicComponentProperties(componentDict::Dict{String, Any}) - divider = componentDict["divider"] + divider = eval(Meta.parse("$(componentDict["divider"])")) + if !(divider isa Rational); divider=rationalize(divider) end amplitude = uparse.(componentDict["amplitude"]) if eltype(amplitude) <: Unitful.Current amplitude = amplitude .|> u"A" @@ -184,7 +185,7 @@ periodicElectricalComponents(channel::PeriodicElectricalChannel) = components(ch export arbitraryElectricalComponents arbitraryElectricalComponents(channel::PeriodicElectricalChannel) = components(channel, ArbitraryElectricalComponent) -cycleDuration(channel::PeriodicElectricalChannel, baseFrequency::typeof(1.0u"Hz")) = lcm([comp.divider for comp in components(channel)])/baseFrequency +cycleDuration(channel::PeriodicElectricalChannel, baseFrequency::typeof(1.0u"Hz")) = lcm([numerator(comp.divider) for comp in components(channel)])/baseFrequency isDfChannel(channel::PeriodicElectricalChannel) = channel.isDfChannel @@ -200,7 +201,7 @@ end export divider, divider! divider(component::ElectricalComponent, trigger::Integer=1) = length(component.divider) == 1 ? component.divider[1] : component.divider[trigger] -divider!(component::PeriodicElectricalComponent,value::Integer) = component.divider = value +divider!(component::PeriodicElectricalComponent,value::Rational) = component.divider = value export amplitude, amplitude! amplitude(component::Union{PeriodicElectricalComponent,ArbitraryElectricalComponent}; period::Integer=1) = component.amplitude[period] diff --git a/src/Sequences/Sequence.jl b/src/Sequences/Sequence.jl index b6fa671f..b2d9d3ca 100644 --- a/src/Sequences/Sequence.jl +++ b/src/Sequences/Sequence.jl @@ -249,7 +249,7 @@ function phase!(channel::PeriodicElectricalChannel, componentId::AbstractString, end export divider! -function divider!(channel::PeriodicElectricalChannel, componentId::AbstractString, value::Integer) +function divider!(channel::PeriodicElectricalChannel, componentId::AbstractString, value::Rational) index = findfirst(x -> id(x) == componentId, channel.components) if !isnothing(index) divider!(channel.components[index], value) @@ -324,7 +324,7 @@ export txBaseFrequency txBaseFrequency(sequence::Sequence) = dfBaseFrequency(sequence) # Alias, since this might not only concern the drivefield export dfSamplesPerCycle -dfSamplesPerCycle(sequence::Sequence) = lcm(dfDivider(sequence)) +dfSamplesPerCycle(sequence::Sequence) = lcm(numerator.(dfDivider(sequence))) export dfCycle dfCycle(sequence::Sequence) = dfSamplesPerCycle(sequence)/dfBaseFrequency(sequence) |> u"s" @@ -336,7 +336,7 @@ export dfDivider function dfDivider(sequence::Sequence) # TODO: How do we integrate the mechanical channels and non-periodic channels and sweeps? channels = dfChannels(sequence) maxComponents = maximum([length(channel.components) for channel in channels]) - result = ones(Int64, (dfNumChannels(sequence), maxComponents)) # Otherwise the dfCycle is miscalculated in the case of a different amount of components + result = ones(Rational, (dfNumChannels(sequence), maxComponents)) # Otherwise the dfCycle is miscalculated in the case of a different amount of components for (channelIdx, channel) in enumerate(channels) for (componentIdx, component) in enumerate(channel.components) @@ -380,7 +380,7 @@ function dfStrength(sequence::Sequence) # TODO: How do we integrate the mechanic for (channelIdx, channel) in enumerate(channels) for (componentIdx, component) in enumerate(channel.components) for (periodIdx, strength) in enumerate(amplitude(component)) # TODO: What do we do if this is in volt? The conversion factor is with the scanner... Remove the volt version? - result[periodIdx, channelIdx, componentIdx] = strength + result[periodIdx, channelIdx, componentIdx] = strength #* 1u"T/V" end end end From 4bfe5f9eb3fe0bb453fe5de3fe8557b92c3ea7c8 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Mon, 15 Apr 2024 16:04:59 +0200 Subject: [PATCH 100/168] fixed DC control (v1) --- src/Devices/Virtual/TxDAQController.jl | 52 ++++++++++++++------------ 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index 5997ef27..13041a25 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -386,15 +386,15 @@ function controlTx(txCont::TxDAQController, control::ControlSequence) updateControlSequence!(dc_cont, signals) setup(daq, dc_cont.currSequence) ref_offsets_δ = measureMeanReferenceSignal(daq, dc_cont.currSequence)[control.refIndices] - - dc_correction = -ref_offsets_0./((ref_offsets_δ.-ref_offsets_0)./δ) + dc_slope = (ref_offsets_δ.-ref_offsets_0)./(δ.-0.0) + dc_correction = -ref_offsets_0./dc_slope @info "Result of finding Zero DC" ref_offsets_0' ref_offsets_δ' dc_correction' if any(abs.(dc_correction).>maximum(δ)) # We do not accept any change that is larger than what we would expect for 1mT error("DC correction too large! Wanted to set $(dc_correction)") end control.dcCorrection = dc_correction - control.dcCalibration = (ref_offsets_δ.-ref_offsets_0)./δ + control.dcCalibration = dc_slope @info "Found DC Calibration" control.dcCalibration end @@ -521,6 +521,8 @@ function getControlResult(cont::ControlSequence)::Sequence for field in fields(cont.targetSequence) if !control(field) push!(_fields, field) + # if there are LUT channels sharing a channel with the controlled fields, we should be able to use the DC calibration that has been found + # and insert it into the corresponding LUT channels as a calibration value end end @@ -570,7 +572,7 @@ end #checkFieldDeviation(cont::ControlSequence, txCont::TxDAQController, uRef) = checkFieldDeviation(cont, txCont, calcFieldFromRef(cont, uRef)) checkFieldDeviation(cont::ControlSequence, txCont::TxDAQController, Γ::Matrix{<:Complex}) = checkFieldDeviation(cont, txCont, Γ, calcDesiredField(cont)) -function checkFieldDeviation(cont::ControlSequence, txCont::TxDAQController, Γ::Matrix{<:Complex}, Ω::Matrix{<:Complex}) +function checkFieldDeviation(cont::CrossCouplingControlSequence, txCont::TxDAQController, Γ::Matrix{<:Complex}, Ω::Matrix{<:Complex}) diff = Ω - Γ abs_deviation = abs.(diff) @@ -579,20 +581,20 @@ function checkFieldDeviation(cont::ControlSequence, txCont::TxDAQController, Γ: phase_deviation = angle.(Ω).-angle.(Γ) phase_deviation[abs.(Ω).<1e-15] .= 0 # phase deviation does not make sense for a zero goal - if !needsDecoupling(cont.targetSequence) && !isa(cont,AWControlSequence) + if !needsDecoupling(cont.targetSequence) abs_deviation = diag(abs_deviation) rel_deviation = diag(rel_deviation) phase_deviation = diag(phase_deviation) - elseif isa(cont, AWControlSequence) - abs_deviation = abs_deviation[allComponentMask(cont)]' # TODO/JA: keep the distinction between the channels (maybe as Vector{Vector{}}), instead of putting everything into a long vector with unknown order - rel_deviation = rel_deviation[allComponentMask(cont)]' - phase_deviation = phase_deviation[allComponentMask(cont)]' - Γt = checkVoltLimits(Γ,cont,return_time_signal=true)' - Ωt = checkVoltLimits(Ω,cont,return_time_signal=true)' + # elseif isa(cont, AWControlSequence) + # abs_deviation = abs_deviation[allComponentMask(cont)]' # TODO/JA: keep the distinction between the channels (maybe as Vector{Vector{}}), instead of putting everything into a long vector with unknown order + # rel_deviation = rel_deviation[allComponentMask(cont)]' + # phase_deviation = phase_deviation[allComponentMask(cont)]' + # Γt = checkVoltLimits(Γ,cont,return_time_signal=true)' + # Ωt = checkVoltLimits(Ω,cont,return_time_signal=true)' - diff = (Ωt .- Γt) - @debug "checkFieldDeviation" diff=lineplot(1:rxNumSamplingPoints(cont.currSequence),diff, canvas=DotCanvas, border=:ascii) - @debug "checkFieldDeviation" max_diff = maximum(abs.(diff)) + # diff = (Ωt .- Γt) + # @debug "checkFieldDeviation" diff=lineplot(1:rxNumSamplingPoints(cont.currSequence),diff, canvas=DotCanvas, border=:ascii) + # @info "checkFieldDeviation2" max_diff = maximum(abs.(diff)) end @debug "Check field deviation [T]" Ω Γ @debug "Ω - Γ = " abs_deviation rel_deviation phase_deviation @@ -609,10 +611,9 @@ function checkFieldDeviation(cont::AWControlSequence, txCont::TxDAQController, Ωt = checkVoltLimits(Ω,cont,return_time_signal=true)' @debug "checkFieldDeviation" Γ[allComponentMask(cont)]' abs.(Γ[allComponentMask(cont)])' angle.(Γ[allComponentMask(cont)])'# abs.(Ω[allComponentMask(cont)])' angle.(Ω[allComponentMask(cont)])' diff = (Ωt .- Γt) - diff = diff .- mean(diff, dims=1) - @debug "checkFieldDeviation" diff=lineplot(1:rxNumSamplingPoints(cont.currSequence),diff, canvas=DotCanvas, border=:ascii) - @info "Observed field deviation:\nmax_diff:\t$(maximum(abs.(diff))*1000) mT" - diff = diff .- mean(diff, dims=1) + zero_mean_diff = diff .- mean(diff, dims=1) + @debug "checkFieldDeviation" diff=lineplot(1:rxNumSamplingPoints(cont.currSequence),diff*1000, canvas=DotCanvas, border=:ascii, ylabel="mT", name=dependency(txCont, AbstractDAQ).refChanIDs[cont.refIndices]) + @info "Observed field deviation (time-domain):\nmax_diff:\t$(maximum(abs.(diff))*1000) mT\nmax_diff (w/o DC): \t$(maximum(abs.(zero_mean_diff))*1000)" amplitude_ok = abs.(diff).< ustrip(u"T", txCont.params.absoluteAmplitudeAccuracy) return all(amplitude_ok) end @@ -655,12 +656,12 @@ function updateControlMatrix(cont::AWControlSequence, txCont::TxDAQController, # The problem is, that to achieve 0 we will always output zero, but we would need a much more sophisticated method to solve this newTx = κ./Γ.*Ω - # handle DC separately: - if txCont.params.findZeroDC - @info "update ControlMatrix" κ[:,1] newTx[:,1] - @info "2" Γ[:,1] (κ[:,1].+(Ω[:,1].-Γ[:,1])./cont.dcCalibration)-cont.dcCorrection - newTx[:,1] .= (κ[:,1].+(Ω[:,1].-Γ[:,1])./cont.dcCalibration)-cont.dcCorrection - end + # # handle DC separately: + # if txCont.params.findZeroDC + # @info "update ControlMatrix" κ[:,1] newTx[:,1] + # @info "2" Γ[:,1] ((κ[:,1].-cont.dcCorrection).*(Ω[:,1]./Γ[:,1])) + # #newTx[:,1] .= (κ[:,1].+(Ω[:,1].-Γ[:,1])./cont.dcCalibration)-cont.dcCorrection + # end #@debug "Last TX matrix [V]:" κ=lineplot(1:rxNumSamplingPoints(cont.currSequence),checkVoltLimits(κ,cont,return_time_signal=true)') #@debug "Ref matrix [T]:" Γ=lineplot(1:rxNumSamplingPoints(cont.currSequence),checkVoltLimits(Γ,cont,return_time_signal=true)') @@ -812,6 +813,9 @@ function calcControlMatrix(cont::AWControlSequence) for (i, channel) in enumerate(getControlledChannels(cont)) if cont.rfftIndices[i,end,1] oldTx[i,1] = ustrip(u"V", offset(channel)) + if !isnothing(cont.dcCorrection) + oldTx[i,1] -= cont.dcCorrection[i] + end end for (j, comp) in enumerate(components(channel)) if isa(comp, PeriodicElectricalComponent) From b0f00d655cdea4556d9f9b72ec304f34036f4492 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Mon, 15 Apr 2024 19:04:29 +0200 Subject: [PATCH 101/168] implemented iterative DC control --- src/Devices/Virtual/TxDAQController.jl | 69 +++++++------------------- 1 file changed, 19 insertions(+), 50 deletions(-) diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index 13041a25..e14788d0 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -34,8 +34,7 @@ mutable struct AWControlSequence <: ControlSequence controlledChannelsDict::OrderedDict{PeriodicElectricalChannel, TxChannelParams} refIndices::Vector{Int64} rfftIndices::BitArray{3} # Matrix of size length(controlledChannelsDict) x 4 (max. num of components) x len(rfft) - dcCorrection::Union{Vector{Float64}, Nothing} - dcCalibration::Union{Vector{Float64}, Nothing} + dcSearch::Vector{@NamedTuple{V::Vector{Float64}, B::Vector{Float64}}} end @enum ControlResult UNCHANGED UPDATED INVALID @@ -111,7 +110,7 @@ function ControlSequence(txCont::TxDAQController, target::Sequence, daq::Abstrac rfftIndices = createRFFTindices(controlledChannelsDict, target, daq) - return AWControlSequence(target, currSeq, controlledChannelsDict, refIndices, rfftIndices, nothing, nothing) + return AWControlSequence(target, currSeq, controlledChannelsDict, refIndices, rfftIndices, []) end end @@ -304,7 +303,7 @@ function measureMeanReferenceSignal(daq::AbstractDAQ, seq::Sequence) @info "DC Control measurement started" producer = @async begin @debug "Starting DC control producer" - endSample = startProducer(channel, daq, 1) + endSample = startProducer(channel, daq, acqNumFrames(seq)) endSequence(daq, endSample) end bind(channel, producer) @@ -319,7 +318,7 @@ function measureMeanReferenceSignal(daq::AbstractDAQ, seq::Sequence) end wait(consumer) - return mean(read(buf.target)[:,channelIdx(daq, daq.refChanIDs),1,1], dims=1) + return mean(read(buf.target),dims=4)[:,channelIdx(daq, daq.refChanIDs),:,:] end function controlTx(txCont::TxDAQController, control::ControlSequence) @@ -367,43 +366,13 @@ function controlTx(txCont::TxDAQController, control::ControlSequence) i = 1 try - if txCont.params.findZeroDC # this is only possible with AWControlSequence - # create sequence with every field off, dc set to zero - # calculate mean for each ref channel - # create sequence with offsets set to small value - # measure men for each ref channel again - # use the two measurements to find the DC value that needs to be output to achieve zero on the reference channel - # always add found value to DC values in control - dc_cont = deepcopy(control) - signals = zeros(ComplexF64,controlMatrixShape(control)) - updateControlSequence!(dc_cont, signals) - setup(daq, dc_cont.currSequence) - ref_offsets_0 = measureMeanReferenceSignal(daq, dc_cont.currSequence)[control.refIndices] - - δ = ustrip.(u"V", [1u"mT"*abs(calibration(daq, id(channel))(0)) for channel in getControlledChannels(control)]) # use DC value for offsets - @debug "findZeroDC: Now outputting the following DC values: [V]" δ - signals[:,1] .= δ - updateControlSequence!(dc_cont, signals) - setup(daq, dc_cont.currSequence) - ref_offsets_δ = measureMeanReferenceSignal(daq, dc_cont.currSequence)[control.refIndices] - dc_slope = (ref_offsets_δ.-ref_offsets_0)./(δ.-0.0) - dc_correction = -ref_offsets_0./dc_slope - - @info "Result of finding Zero DC" ref_offsets_0' ref_offsets_δ' dc_correction' - if any(abs.(dc_correction).>maximum(δ)) # We do not accept any change that is larger than what we would expect for 1mT - error("DC correction too large! Wanted to set $(dc_correction)") - end - control.dcCorrection = dc_correction - control.dcCalibration = dc_slope - @info "Found DC Calibration" control.dcCalibration - end while !controlPhaseDone && i <= txCont.params.maxControlSteps @info "CONTROL STEP $i" # Prepare control measurement setup(daq, control.currSequence) - if haskey(ENV, "JULIA_DEBUG") && "checkEachControlStep" in split(ENV["JULIA_DEBUG"],",") + if haskey(ENV, "JULIA_DEBUG") && contains(ENV["JULIA_DEBUG"],"checkEachControlStep") menu = REPL.TerminalMenus.RadioMenu(["Continue", "Abort"], pagesize=2) choice = REPL.TerminalMenus.request("Please confirm the current values for control:", menu) if choice == 1 @@ -656,12 +625,20 @@ function updateControlMatrix(cont::AWControlSequence, txCont::TxDAQController, # The problem is, that to achieve 0 we will always output zero, but we would need a much more sophisticated method to solve this newTx = κ./Γ.*Ω - # # handle DC separately: - # if txCont.params.findZeroDC - # @info "update ControlMatrix" κ[:,1] newTx[:,1] - # @info "2" Γ[:,1] ((κ[:,1].-cont.dcCorrection).*(Ω[:,1]./Γ[:,1])) - # #newTx[:,1] .= (κ[:,1].+(Ω[:,1].-Γ[:,1])./cont.dcCalibration)-cont.dcCorrection - # end + # handle DC separately: + if txCont.params.findZeroDC + push!(cont.dcSearch, (V=κ[:,1], B=Γ[:,1])) + if length(cont.dcSearch)==1 + my_sign(x) = if x<0; -1 else 1 end + testOffset = real.(Ω[:,1])*u"T" .- 2u"mT"*my_sign.(real.(Ω[:,1])) + newTx[:,1] = ustrip.(u"V", testOffset.*[abs(calibration(dependency(txCont, AbstractDAQ), id(channel))(0)) for channel in getControlledChannels(cont)]) + else + @debug "History of dcSearch" cont.dcSearch + last = cont.dcSearch[end] + previous = cont.dcSearch[end-1] + newTx[:,1] .= previous.V .- ((previous.B.-Ω[:,1]).*(last.V.-previous.V))./(last.B.-previous.B) + end + end #@debug "Last TX matrix [V]:" κ=lineplot(1:rxNumSamplingPoints(cont.currSequence),checkVoltLimits(κ,cont,return_time_signal=true)') #@debug "Ref matrix [T]:" Γ=lineplot(1:rxNumSamplingPoints(cont.currSequence),checkVoltLimits(Γ,cont,return_time_signal=true)') @@ -813,9 +790,6 @@ function calcControlMatrix(cont::AWControlSequence) for (i, channel) in enumerate(getControlledChannels(cont)) if cont.rfftIndices[i,end,1] oldTx[i,1] = ustrip(u"V", offset(channel)) - if !isnothing(cont.dcCorrection) - oldTx[i,1] -= cont.dcCorrection[i] - end end for (j, comp) in enumerate(components(channel)) if isa(comp, PeriodicElectricalComponent) @@ -842,12 +816,7 @@ end function updateControlSequence!(cont::AWControlSequence, newTx::Matrix) for (i, channel) in enumerate(periodicElectricalTxChannels(cont.currSequence)) if cont.rfftIndices[i,end,1] - if !isnothing(cont.dcCorrection) - @debug "updateControlSequence" (cont.dcCorrection[i]+real(newTx[i,1]))*1.0u"V" - offset!(channel, (cont.dcCorrection[i]+real(newTx[i,1]))*1.0u"V") - else offset!(channel, real(newTx[i,1])*1.0u"V") - end end for (j, comp) in enumerate(components(channel)) if isa(comp, PeriodicElectricalComponent) From 0e9556f06ddb7d55d1657c7cd177c6ccb1307d57 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Tue, 16 Apr 2024 14:18:06 +0200 Subject: [PATCH 102/168] remove preparation of DAQ, fix controlTx --- .../MultiSequenceSystemMatrixProtocol.jl | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/Protocols/MultiSequenceSystemMatrixProtocol.jl b/src/Protocols/MultiSequenceSystemMatrixProtocol.jl index 2e273f06..7c9b5be3 100644 --- a/src/Protocols/MultiSequenceSystemMatrixProtocol.jl +++ b/src/Protocols/MultiSequenceSystemMatrixProtocol.jl @@ -203,12 +203,11 @@ function performMeasurements(protocol::MultiSequenceSystemMatrixProtocol) end wasRestored = protocol.restored - timePreparing = @elapsed begin + timeWaited = @elapsed begin wait(calib.producer) wait(calib.consumer) - prepareDAQ(protocol) end - diffTime = protocol.params.waitTime - timePreparing + diffTime = protocol.params.waitTime - timeWaited if diffTime > 0.0 && !wasRestored && protocol.systemMeasState.currPos > 1 @info "Wait $diffTime s for next measurement" sleep(diffTime) @@ -244,8 +243,9 @@ function performMeasurement(protocol::MultiSequenceSystemMatrixProtocol) sequence = protocol.params.sequences[index] if protocol.params.controlTx - sequence = protocol.contSequence.targetSequence + sequence = controlTx(protocol.txCont, sequence) end + setup(daq, sequence) channel = Channel{channelType(daq)}(32) calib.producer = @tspawnat protocol.scanner.generalParams.producerThreadID asyncProducer(channel, daq, sequence) @@ -261,29 +261,29 @@ function performMeasurement(protocol::MultiSequenceSystemMatrixProtocol) calib.currPos += 1 end -function prepareDAQ(protocol::MultiSequenceSystemMatrixProtocol) - calib = protocol.systemMeasState - daq = getDAQ(protocol.scanner) - - sequence = protocol.params.sequences[calib.currPos] - if protocol.params.controlTx - if isnothing(protocol.contSequence) || protocol.restored || (calib.currPos == 1) - protocol.contSequence = controlTx(protocol.txCont, protocol.params.sequence) - end - #if isempty(protocol.systemMeasState.drivefield) - # len = length(keys(protocol.contSequence.simpleChannel)) - # drivefield = zeros(ComplexF64, len, len, size(calib.signals, 3), size(calib.signals, 4)) - # calib.drivefield = mmap!(protocol, "observedField.bin", drivefield) - # applied = zeros(ComplexF64, len, len, size(calib.signals, 3), size(calib.signals, 4)) - # calib.applied = mmap!(protocol, "appliedFiled.bin", applied) - #end - sequence = protocol.contSequence - end - setup(daq, sequence) - if protocol.restored - protocol.restored = false - end -end +# function prepareDAQ(protocol::MultiSequenceSystemMatrixProtocol) +# calib = protocol.systemMeasState +# daq = getDAQ(protocol.scanner) + +# sequence = protocol.params.sequences[calib.currPos] +# if protocol.params.controlTx +# if isnothing(protocol.contSequence) || protocol.restored || (calib.currPos == 1) +# sequence = controlTx(protocol.txCont, sequence) +# end +# #if isempty(protocol.systemMeasState.drivefield) +# # len = length(keys(protocol.contSequence.simpleChannel)) +# # drivefield = zeros(ComplexF64, len, len, size(calib.signals, 3), size(calib.signals, 4)) +# # calib.drivefield = mmap!(protocol, "observedField.bin", drivefield) +# # applied = zeros(ComplexF64, len, len, size(calib.signals, 3), size(calib.signals, 4)) +# # calib.applied = mmap!(protocol, "appliedFiled.bin", applied) +# #end +# #sequence = protocol.contSequence +# end +# setup(daq, sequence) +# if protocol.restored +# protocol.restored = false +# end +# end function asyncConsumer(channel::Channel, protocol::MultiSequenceSystemMatrixProtocol, index) calib = protocol.systemMeasState From 64811e43ea95a488f55fa7ff53be5132c6d12455 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Tue, 16 Apr 2024 14:18:38 +0200 Subject: [PATCH 103/168] small fixes --- src/Protocols/MPSMeasurementProtocol.jl | 2 +- src/Protocols/Storage/ProducerConsumer.jl | 4 +--- src/Sequences/PeriodicElectricalChannel.jl | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Protocols/MPSMeasurementProtocol.jl b/src/Protocols/MPSMeasurementProtocol.jl index d0aa8763..0112b235 100644 --- a/src/Protocols/MPSMeasurementProtocol.jl +++ b/src/Protocols/MPSMeasurementProtocol.jl @@ -153,7 +153,7 @@ function timeEstimate(protocol::MPSMeasurementProtocol) end end perc_wait = round(Int,sum(isnothing.(protocol.patchPermutation))/size(protocol.patchPermutation,1)*100) - est = "FG: $(timeFormat(fgTime)) ($(perc_wait)% waiting), BG: $(timeFormat(bgTime))" + est = "FG: $(timeFormat(fgTime)) ($(perc_wait)% waiting)\nBG: $(timeFormat(bgTime))" @info "The estimated duration is FG: $fgTime ($(perc_wait)% waiting), BG: $bgTime." end return est diff --git a/src/Protocols/Storage/ProducerConsumer.jl b/src/Protocols/Storage/ProducerConsumer.jl index aee96c16..6660a31a 100644 --- a/src/Protocols/Storage/ProducerConsumer.jl +++ b/src/Protocols/Storage/ProducerConsumer.jl @@ -7,9 +7,7 @@ function SequenceMeasState(daq::RedPitayaDAQ, sequence::ControlSequence, sequenc frameAvgs = acqNumFrameAverages(sequence.targetSequence) if frameAvgs > 1 samples = rxNumSamplesPerPeriod(sequence.targetSequence) - @info daq.refChanIDs length(daq.refChanIDs) - #TODO/JA: replace hardcoded 3 - buffer = AverageBuffer(buffer, samples, 3, numPeriods, frameAvgs) + buffer = AverageBuffer(buffer, samples, length(daq.refChanIDs), numPeriods, frameAvgs) end return SequenceMeasState(daq, sequence.targetSequence, push!(sequenceBuffer, buffer)) end diff --git a/src/Sequences/PeriodicElectricalChannel.jl b/src/Sequences/PeriodicElectricalChannel.jl index a853c8c3..9790e681 100644 --- a/src/Sequences/PeriodicElectricalChannel.jl +++ b/src/Sequences/PeriodicElectricalChannel.jl @@ -120,7 +120,7 @@ function createFieldChannel(channelID::AbstractString, ::Type{PeriodicElectrical splattingDict[:isDfChannel] = channelDict["isDfChannel"] end if haskey(channelDict, "dcEnabled") - splattingDict[:isDfChannel] = channelDict["dcEnabled"] + splattingDict[:dcEnabled] = channelDict["dcEnabled"] end components = Vector{ElectricalComponent}() From 8e44afe125caa5ce6f639cb8a25526fc73afb467 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Tue, 16 Apr 2024 14:20:57 +0200 Subject: [PATCH 104/168] fixed adjoint/transpose issue --- src/Devices/Virtual/TxDAQController.jl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index e14788d0..6ead6438 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -576,9 +576,9 @@ end function checkFieldDeviation(cont::AWControlSequence, txCont::TxDAQController, Γ::Matrix{<:Complex}, Ω::Matrix{<:Complex}) - Γt = checkVoltLimits(Γ,cont,return_time_signal=true)' - Ωt = checkVoltLimits(Ω,cont,return_time_signal=true)' - @debug "checkFieldDeviation" Γ[allComponentMask(cont)]' abs.(Γ[allComponentMask(cont)])' angle.(Γ[allComponentMask(cont)])'# abs.(Ω[allComponentMask(cont)])' angle.(Ω[allComponentMask(cont)])' + Γt = transpose(checkVoltLimits(Γ,cont,return_time_signal=true)) + Ωt = transpose(checkVoltLimits(Ω,cont,return_time_signal=true)) + @debug "checkFieldDeviation" transpose(Γ[allComponentMask(cont)]) abs.(Γ[allComponentMask(cont)])' angle.(Γ[allComponentMask(cont)])'# abs.(Ω[allComponentMask(cont)])' angle.(Ω[allComponentMask(cont)])' diff = (Ωt .- Γt) zero_mean_diff = diff .- mean(diff, dims=1) @debug "checkFieldDeviation" diff=lineplot(1:rxNumSamplingPoints(cont.currSequence),diff*1000, canvas=DotCanvas, border=:ascii, ylabel="mT", name=dependency(txCont, AbstractDAQ).refChanIDs[cont.refIndices]) @@ -695,7 +695,7 @@ function calcFieldsFromRef(cont::AWControlSequence, uRef::Array{Float32,4}) spectrum[1,:,:,:] ./= 2 sortedSpectrum = permutedims(spectrum[:, cont.refIndices, :, :], (2,1,3,4)) frequencies = ustrip.(u"Hz",rfftfreq(N, rxSamplingRate(cont.currSequence))) - fb_calibration = reduce(vcat, [ustrip.(u"T/V", chan.feedback.calibration(frequencies)) for chan in getControlledDAQChannels(cont)]') + fb_calibration = reduce(vcat, transpose([ustrip.(u"T/V", chan.feedback.calibration(frequencies)) for chan in getControlledDAQChannels(cont)])) return sortedSpectrum.*fb_calibration end @@ -861,7 +861,7 @@ end function checkFieldToVolt(oldTx::Matrix{<:Complex}, Γ::Matrix{<:Complex}, cont::AWControlSequence, txCont::TxDAQController, Ω::Matrix{<:Complex}) N = rxNumSamplingPoints(cont.currSequence) frequencies = ustrip.(u"Hz",rfftfreq(N, rxSamplingRate(cont.currSequence))) - calibFieldToVoltEstimate = reduce(vcat,[ustrip.(u"V/T", chan.calibration(frequencies)) for chan in getControlledDAQChannels(cont)]') + calibFieldToVoltEstimate = reduce(vcat,transpose([ustrip.(u"V/T", chan.calibration(frequencies)) for chan in getControlledDAQChannels(cont)])) calibFieldToVoltMeasured = oldTx ./ Γ mask = allComponentMask(cont) .& (abs.(Ω).>1e-15) From 9532037fb4abd82e7b4db9fd0a8322e10e20ed70 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Tue, 16 Apr 2024 14:21:17 +0200 Subject: [PATCH 105/168] update controller params --- src/Devices/Virtual/TxDAQController.jl | 53 +++++++++----------------- 1 file changed, 17 insertions(+), 36 deletions(-) diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index 6ead6438..350e82df 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -3,11 +3,11 @@ export TxDAQControllerParams, TxDAQController, controlTx Base.@kwdef mutable struct TxDAQControllerParams <: DeviceParams phaseAccuracy::typeof(1.0u"rad") relativeAmplitudeAccuracy::Float64 - controlPause::Float64 absoluteAmplitudeAccuracy::typeof(1.0u"T") = 50.0u"µT" maxControlSteps::Int64 = 20 fieldToVoltDeviation::Float64 = 0.2 - findZeroDC::Bool = false + controlDC::Bool = false + timeUntilStable::Float64 = 0.0 minimumStepDuration::Float64 = 0.002 end TxDAQControllerParams(dict::Dict) = params_from_dict(TxDAQControllerParams, dict) @@ -44,6 +44,7 @@ Base.@kwdef mutable struct TxDAQController <: VirtualDevice ref::Union{Array{Float32, 4}, Nothing} = nothing # TODO remove when done cont::Union{Nothing, ControlSequence} = nothing # TODO remove when done + startFrame::Int64 = 1 end function _init(tx::TxDAQController) @@ -65,8 +66,12 @@ function ControlSequence(txCont::TxDAQController, target::Sequence, daq::Abstrac currSeq = prepareSequenceForControl(target) - duration = div(txCont.params.minimumStepDuration, ustrip(u"s", dfCycle(currSeq)), RoundUp) - acqNumFrames(currSeq, max(duration, 1)) + measuredFrames = max(cld(txCont.params.minimumStepDuration, ustrip(u"s", dfCycle(currSeq))),1) + discardedFrames = cld(txCont.params.timeUntilStable, ustrip(u"s", dfCycle(currSeq))) + txCont.startFrame = discardedFrames + 1 + acqNumFrames(currSeq, discardedFrames+measuredFrames) + + applyForwardCalibration!(currSeq, daq) # uses the forward calibration to convert the values for the field from T to V @@ -78,7 +83,7 @@ function ControlSequence(txCont::TxDAQController, target::Sequence, daq::Abstrac controlledChannelsDict = createControlledChannelsDict(seqControlledChannels, daq) # should this be changed to components instead of channels? refIndices = createReferenceIndexMapping(controlledChannelsDict, daq) - controlSequenceType = decideControlSequenceType(target, txCont.params.findZeroDC) + controlSequenceType = decideControlSequenceType(target, txCont.params.controlDC) @debug "ControlSequence: Decided on using ControlSequence of type $controlSequenceType" if controlSequenceType==CrossCouplingControlSequence @@ -114,20 +119,20 @@ function ControlSequence(txCont::TxDAQController, target::Sequence, daq::Abstrac end end -function decideControlSequenceType(target::Sequence, findZeroDC::Bool=false) +function decideControlSequenceType(target::Sequence, controlDC::Bool=false) hasAWComponent = any(isa.([component for channel in getControlledChannels(target) for component in channel.components], ArbitraryElectricalComponent)) moreThanOneComponent = any(x -> length(x.components) > 1, getControlledChannels(target)) moreThanThreeChannels = length(getControlledChannels(target)) > 3 moreThanOneField = length(getControlledFields(target)) > 1 needsDecoupling_ = needsDecoupling(target) - @debug "decideControlSequenceType:" hasAWComponent moreThanOneComponent moreThanThreeChannels moreThanOneField needsDecoupling_ findZeroDC + @debug "decideControlSequenceType:" hasAWComponent moreThanOneComponent moreThanThreeChannels moreThanOneField needsDecoupling_ controlDC - if needsDecoupling_ && !hasAWComponent && !moreThanOneField && !moreThanThreeChannels && !moreThanOneComponent && !findZeroDC + if needsDecoupling_ && !hasAWComponent && !moreThanOneField && !moreThanThreeChannels && !moreThanOneComponent && !controlDC return CrossCouplingControlSequence elseif needsDecoupling_ - throw(SequenceConfigurationError("The given sequence can not be controlled! To control a field with decoupling it cannot have an AW component ($hasAWComponent), more than one field ($moreThanOneField), more than three channels ($moreThanThreeChannels) nor more than one component per channel ($moreThanOneComponent). DC control ($findZeroDC) is also not possible")) - elseif !hasAWComponent && !moreThanOneField && !moreThanThreeChannels && !moreThanOneComponent && !findZeroDC + throw(SequenceConfigurationError("The given sequence can not be controlled! To control a field with decoupling it cannot have an AW component ($hasAWComponent), more than one field ($moreThanOneField), more than three channels ($moreThanThreeChannels) nor more than one component per channel ($moreThanOneComponent). DC control ($controlDC) is also not possible")) + elseif !hasAWComponent && !moreThanOneField && !moreThanThreeChannels && !moreThanOneComponent && !controlDC return CrossCouplingControlSequence else return AWControlSequence @@ -297,30 +302,6 @@ function controlTx(txCont::TxDAQController, seq::Sequence, ::Nothing = nothing) end end -function measureMeanReferenceSignal(daq::AbstractDAQ, seq::Sequence) - channel = Channel{channelType(daq)}(32) - buf = AsyncBuffer(FrameBuffer(1, zeros(Float32,rxNumSamplesPerPeriod(seq),length(daq.rpv)*2,acqNumPeriodsPerFrame(seq),acqNumFrames(seq))), daq) - @info "DC Control measurement started" - producer = @async begin - @debug "Starting DC control producer" - endSample = startProducer(channel, daq, acqNumFrames(seq)) - endSequence(daq, endSample) - end - bind(channel, producer) - consumer = @async begin - while isopen(channel) || isready(channel) - while isready(channel) - chunk = take!(channel) - push!(buf, chunk) - end - sleep(0.001) - end - end - - wait(consumer) - return mean(read(buf.target),dims=4)[:,channelIdx(daq, daq.refChanIDs),:,:] -end - function controlTx(txCont::TxDAQController, control::ControlSequence) # Prepare and check channel under control daq = dependency(txCont, AbstractDAQ) @@ -409,7 +390,7 @@ function controlTx(txCont::TxDAQController, control::ControlSequence) @debug "Size of calc fields from ref" size(tmp) # TODO: Fix this to ensure, that the number of frames is alwys large enough (maybe add a parameter riseup time) - Γ = mean(tmp[:, :, 1, 1:end],dims=3)[:,:,1] # calcFieldsFromRef happened here already + Γ = mean(tmp[:, :, 1, txCont.startFrame:end],dims=3)[:,:,1] # calcFieldsFromRef happened here already if !isnothing(Γ) controlPhaseDone = controlStep!(control, txCont, Γ, Ω) == UNCHANGED if controlPhaseDone @@ -626,7 +607,7 @@ function updateControlMatrix(cont::AWControlSequence, txCont::TxDAQController, newTx = κ./Γ.*Ω # handle DC separately: - if txCont.params.findZeroDC + if txCont.params.controlDC push!(cont.dcSearch, (V=κ[:,1], B=Γ[:,1])) if length(cont.dcSearch)==1 my_sign(x) = if x<0; -1 else 1 end From b25b66b5d78bed20bd7c6ed88560137cf69543cd Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Tue, 23 Apr 2024 10:09:43 +0200 Subject: [PATCH 106/168] add caching to getConcreteType --- src/Scanner.jl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Scanner.jl b/src/Scanner.jl index b3ad3bbc..3e3741e6 100644 --- a/src/Scanner.jl +++ b/src/Scanner.jl @@ -26,7 +26,11 @@ end Retrieve the concrete type of a given supertype corresponding to a given string. """ +concreteTypesCache = Dict{String, Type}() function getConcreteType(supertype_::Type, type::String) + if haskey(concreteTypesCache, type) + return concreteTypesCache[type] + end knownTypes = deepsubtypes(supertype_) foundImplementation = nothing for Implementation in knownTypes @@ -34,6 +38,7 @@ function getConcreteType(supertype_::Type, type::String) foundImplementation = Implementation end end + push!(concreteTypesCache, type=>foundImplementation) return foundImplementation end From 9fb3d032bde5b760c982a8cfded6a0f3d5859c0c Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Tue, 23 Apr 2024 10:11:16 +0200 Subject: [PATCH 107/168] include original error in exception if control fails --- src/Devices/Virtual/TxDAQController.jl | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index 350e82df..d3d576f0 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -344,6 +344,7 @@ function controlTx(txCont::TxDAQController, control::ControlSequence) # Hacky solution controlPhaseDone = false + controlPhaseError = nothing i = 1 try @@ -405,6 +406,7 @@ function controlTx(txCont::TxDAQController, control::ControlSequence) end catch ex @error "Exception during control loop" exception=(ex, catch_backtrace()) + controlPhaseError = ex finally try stopTx(daq) @@ -439,7 +441,11 @@ function controlTx(txCont::TxDAQController, control::ControlSequence) end if !controlPhaseDone - error("TxDAQController $(deviceID(txCont)) could not control.") + if isnothing(controlPhaseError) + error("TxDAQController $(deviceID(txCont)) could not control.") + else + error("TxDAQController $(deviceID(txCont)) failed control with the following message:\n$(sprint(showerror, controlPhaseError))") + end end return control From dab3a44536dabcb6cfecc1b2c81e427213bd16b6 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Tue, 23 Apr 2024 10:12:04 +0200 Subject: [PATCH 108/168] new logic to validate signals (fieldToVolt) --- src/Devices/Virtual/TxDAQController.jl | 67 ++++++++++++++++++++------ 1 file changed, 51 insertions(+), 16 deletions(-) diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index d3d576f0..18d3f6f4 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -5,10 +5,13 @@ Base.@kwdef mutable struct TxDAQControllerParams <: DeviceParams relativeAmplitudeAccuracy::Float64 absoluteAmplitudeAccuracy::typeof(1.0u"T") = 50.0u"µT" maxControlSteps::Int64 = 20 - fieldToVoltDeviation::Float64 = 0.2 + #fieldToVoltDeviation::Float64 = 0.2 controlDC::Bool = false timeUntilStable::Float64 = 0.0 minimumStepDuration::Float64 = 0.002 + fieldToVoltRelDeviation::Float64 = 0.2 + fieldToVoltAbsDeviation::typeof(1.0u"T") = 5.0u"mT" + maxField::typeof(1.0u"T") = 40.0u"mT" end TxDAQControllerParams(dict::Dict) = params_from_dict(TxDAQControllerParams, dict) @@ -389,7 +392,6 @@ function controlTx(txCont::TxDAQController, control::ControlSequence) @info "Evaluating control step" tmp = read(sink(buffer, DriveFieldBuffer)) @debug "Size of calc fields from ref" size(tmp) - # TODO: Fix this to ensure, that the number of frames is alwys large enough (maybe add a parameter riseup time) Γ = mean(tmp[:, :, 1, txCont.startFrame:end],dims=3)[:,:,1] # calcFieldsFromRef happened here already if !isnothing(Γ) @@ -517,7 +519,7 @@ controlMatrixShape(cont::CrossCouplingControlSequence) = (numControlledChannels( controlStep!(cont::ControlSequence, txCont::TxDAQController, uRef) = controlStep!(cont, txCont, uRef, calcDesiredField(cont)) controlStep!(cont::ControlSequence, txCont::TxDAQController, uRef, Ω::Matrix{<:Complex}) = controlStep!(cont, txCont, calcFieldsFromRef(cont, uRef), Ω) function controlStep!(cont::ControlSequence, txCont::TxDAQController, Γ::Matrix{<:Complex}, Ω::Matrix{<:Complex}) - if checkFieldDeviation(cont, txCont, Γ, Ω) + if fieldAccuracyReached(cont, txCont, Γ, Ω) return UNCHANGED elseif updateControl!(cont, txCont, Γ, Ω) return UPDATED @@ -526,9 +528,9 @@ function controlStep!(cont::ControlSequence, txCont::TxDAQController, Γ::Matrix end end -#checkFieldDeviation(cont::ControlSequence, txCont::TxDAQController, uRef) = checkFieldDeviation(cont, txCont, calcFieldFromRef(cont, uRef)) -checkFieldDeviation(cont::ControlSequence, txCont::TxDAQController, Γ::Matrix{<:Complex}) = checkFieldDeviation(cont, txCont, Γ, calcDesiredField(cont)) -function checkFieldDeviation(cont::CrossCouplingControlSequence, txCont::TxDAQController, Γ::Matrix{<:Complex}, Ω::Matrix{<:Complex}) +#fieldAccuracyReached(cont::ControlSequence, txCont::TxDAQController, uRef) = fieldAccuracyReached(cont, txCont, calcFieldFromRef(cont, uRef)) +fieldAccuracyReached(cont::ControlSequence, txCont::TxDAQController, Γ::Matrix{<:Complex}) = fieldAccuracyReached(cont, txCont, Γ, calcDesiredField(cont)) +function fieldAccuracyReached(cont::CrossCouplingControlSequence, txCont::TxDAQController, Γ::Matrix{<:Complex}, Ω::Matrix{<:Complex}) diff = Ω - Γ abs_deviation = abs.(diff) @@ -549,8 +551,8 @@ function checkFieldDeviation(cont::CrossCouplingControlSequence, txCont::TxDAQCo # Ωt = checkVoltLimits(Ω,cont,return_time_signal=true)' # diff = (Ωt .- Γt) - # @debug "checkFieldDeviation" diff=lineplot(1:rxNumSamplingPoints(cont.currSequence),diff, canvas=DotCanvas, border=:ascii) - # @info "checkFieldDeviation2" max_diff = maximum(abs.(diff)) + # @debug "fieldAccuracyReached" diff=lineplot(1:rxNumSamplingPoints(cont.currSequence),diff, canvas=DotCanvas, border=:ascii) + # @info "fieldAccuracyReached2" max_diff = maximum(abs.(diff)) end @debug "Check field deviation [T]" Ω Γ @debug "Ω - Γ = " abs_deviation rel_deviation phase_deviation @@ -561,14 +563,14 @@ function checkFieldDeviation(cont::CrossCouplingControlSequence, txCont::TxDAQCo return all(phase_ok) && all(amplitude_ok) end -function checkFieldDeviation(cont::AWControlSequence, txCont::TxDAQController, Γ::Matrix{<:Complex}, Ω::Matrix{<:Complex}) +function fieldAccuracyReached(cont::AWControlSequence, txCont::TxDAQController, Γ::Matrix{<:Complex}, Ω::Matrix{<:Complex}) Γt = transpose(checkVoltLimits(Γ,cont,return_time_signal=true)) Ωt = transpose(checkVoltLimits(Ω,cont,return_time_signal=true)) - @debug "checkFieldDeviation" transpose(Γ[allComponentMask(cont)]) abs.(Γ[allComponentMask(cont)])' angle.(Γ[allComponentMask(cont)])'# abs.(Ω[allComponentMask(cont)])' angle.(Ω[allComponentMask(cont)])' + @debug "fieldAccuracyReached" transpose(Γ[allComponentMask(cont)]) abs.(Γ[allComponentMask(cont)])' angle.(Γ[allComponentMask(cont)])'# abs.(Ω[allComponentMask(cont)])' angle.(Ω[allComponentMask(cont)])' diff = (Ωt .- Γt) zero_mean_diff = diff .- mean(diff, dims=1) - @debug "checkFieldDeviation" diff=lineplot(1:rxNumSamplingPoints(cont.currSequence),diff*1000, canvas=DotCanvas, border=:ascii, ylabel="mT", name=dependency(txCont, AbstractDAQ).refChanIDs[cont.refIndices]) + @debug "fieldAccuracyReached" diff=lineplot(1:rxNumSamplingPoints(cont.currSequence),diff*1000, canvas=DotCanvas, border=:ascii, ylabel="mT", name=dependency(txCont, AbstractDAQ).refChanIDs[cont.refIndices]) @info "Observed field deviation (time-domain):\nmax_diff:\t$(maximum(abs.(diff))*1000) mT\nmax_diff (w/o DC): \t$(maximum(abs.(zero_mean_diff))*1000)" amplitude_ok = abs.(diff).< ustrip(u"T", txCont.params.absoluteAmplitudeAccuracy) return all(amplitude_ok) @@ -579,9 +581,13 @@ updateControl!(cont::ControlSequence, txCont::TxDAQController, uRef) = updateCon function updateControl!(cont::ControlSequence, txCont::TxDAQController, Γ::Matrix{<:Complex}, Ω::Matrix{<:Complex}) @debug "Updating control values" κ = calcControlMatrix(cont) + + if !validateAgainstForwardCalibrationAndSafetyLimit(κ, Γ, cont, txCont) + error("Last control step produced unexpected results! Either your forward calibration is inaccurate or the system is not in the expected state (e.g. amp not on)!" ) + end newTx = updateControlMatrix(cont, txCont, Γ, Ω, κ) - if checkFieldToVolt(κ, Γ, cont, txCont, Ω) && checkVoltLimits(newTx, cont) + if validateAgainstForwardCalibrationAndSafetyLimit(newTx, Ω, cont, txCont) && checkVoltLimits(newTx, cont) updateControlSequence!(cont, newTx) return true else @@ -825,6 +831,35 @@ end ################################################################################# +function calcExpectedField(tx::Matrix{<:Complex}, cont::CrossCouplingControlSequence) + dividers = divider.(getPrimaryComponents(cont)) + frequencies = ustrip(u"Hz", txBaseFrequency(cont.currSequence)) ./ dividers + calibFieldToVoltEstimate = [ustrip(u"V/T", chan.calibration(frequencies[i])) for (i,chan) in enumerate(getControlledDAQChannels(cont))] + B_fw = tx ./ calibFieldToVoltEstimate + return B_fw +end + +function calcExpectedField(tx::Matrix{<:Complex}, cont::AWControlSequence) + N = rxNumSamplingPoints(cont.currSequence) + frequencies = ustrip.(u"Hz",rfftfreq(N, rxSamplingRate(cont.currSequence))) + calibFieldToVoltEstimate = reduce(vcat,transpose([ustrip.(u"V/T", chan.calibration(frequencies)) for chan in getControlledDAQChannels(cont)])) + B_fw = tx ./ calibFieldToVoltEstimate + return B_fw +end + +function validateAgainstForwardCalibrationAndSafetyLimit(tx::Matrix{<:Complex}, B::Matrix{<:Complex}, cont::ControlSequence, txCont::TxDAQController) + # step 1 apply forward calibration to tx -> B_fw + B_fw = calcExpectedField(tx, cont) + + # step 2 check B_fw against B (rel. and abs. Accuracy) + forwardCalibrationAgrees = isapprox.(abs.(B_fw), abs.(B), rtol = txCont.params.fieldToVoltRelDeviation, atol=ustrip(u"T",txCont.params.fieldToVoltAbsDeviation)) + + @debug "validateAgainstForwardCalibrationAndSafetyLimit" abs.(B_fw) abs.(B) forwardCalibrationAgrees + # step 3 check if B_fw and B are both below safety limit + isSafe(Btest) = abs.(Btest). 10/180*pi @warn "The phase of the measured field to volt deviates by $phase_deviation from estimate. Please check you phases! Continuing anyways..." end @@ -855,9 +890,9 @@ function checkFieldToVolt(oldTx::Matrix{<:Complex}, Γ::Matrix{<:Complex}, cont: abs_deviation = abs.(1.0 .- abs.(calibFieldToVoltMeasured[mask])./abs.(calibFieldToVoltEstimate[mask])) phase_deviation = angle.(calibFieldToVoltMeasured[mask]) .- angle.(calibFieldToVoltEstimate[mask]) @debug "checkFieldToVolt: We expected $(calibFieldToVoltEstimate[mask]) V/T and got $(calibFieldToVoltMeasured[mask]) V/T, deviation: $abs_deviation" - valid = maximum( abs_deviation ) < txCont.params.fieldToVoltDeviation + valid = maximum( abs_deviation ) < txCont.params.fieldToVoltRelDeviation if !valid - @error "Measured field to volt deviates by $(abs_deviation*100) % from estimate, exceeding allowed deviation of $(txCont.params.fieldToVoltDeviation*100) %" + @error "Measured field to volt deviates by $(abs_deviation*100) % from estimate, exceeding allowed deviation of $(txCont.params.fieldToVoltRelDeviation*100) %" elseif maximum(abs.(phase_deviation)) > 10/180*pi @warn "The phase of the measured field to volt deviates by $phase_deviation from estimate. Please check you phases! Continuing anyways..." end From b06de7b5ca56deefee034dd674126585ef0c5776 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Thu, 25 Apr 2024 12:37:46 +0200 Subject: [PATCH 109/168] improved error messages --- src/Devices/Virtual/TxDAQController.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index 18d3f6f4..4ee775c5 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -444,7 +444,7 @@ function controlTx(txCont::TxDAQController, control::ControlSequence) if !controlPhaseDone if isnothing(controlPhaseError) - error("TxDAQController $(deviceID(txCont)) could not control.") + error("TxDAQController $(deviceID(txCont)) could not reach a stable field after $(txCont.params.maxControlSteps) steps.") else error("TxDAQController $(deviceID(txCont)) failed control with the following message:\n$(sprint(showerror, controlPhaseError))") end @@ -591,7 +591,7 @@ function updateControl!(cont::ControlSequence, txCont::TxDAQController, Γ::Matr updateControlSequence!(cont, newTx) return true else - @warn "New control values are not allowed" + error("The new tx values are not allowed! Either your forward calibration is inaccurate or the system can not produce the requested field strength!") return false end end @@ -854,7 +854,7 @@ function validateAgainstForwardCalibrationAndSafetyLimit(tx::Matrix{<:Complex}, # step 2 check B_fw against B (rel. and abs. Accuracy) forwardCalibrationAgrees = isapprox.(abs.(B_fw), abs.(B), rtol = txCont.params.fieldToVoltRelDeviation, atol=ustrip(u"T",txCont.params.fieldToVoltAbsDeviation)) - @debug "validateAgainstForwardCalibrationAndSafetyLimit" abs.(B_fw) abs.(B) forwardCalibrationAgrees + @debug "validateAgainstForwardCalibrationAndSafetyLimit" abs.(B_fw) abs.(B) forwardCalibrationAgrees isSafe(B_fw) isSafe(B) # step 3 check if B_fw and B are both below safety limit isSafe(Btest) = abs.(Btest). Date: Thu, 25 Apr 2024 12:38:08 +0200 Subject: [PATCH 110/168] fix restoring MultiSequence Protocol --- .../MultiSequenceSystemMatrixProtocol.jl | 26 +++++++++++++------ .../RobotBasedSystemMatrixProtocol.jl | 2 +- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/Protocols/MultiSequenceSystemMatrixProtocol.jl b/src/Protocols/MultiSequenceSystemMatrixProtocol.jl index 7c9b5be3..55531aee 100644 --- a/src/Protocols/MultiSequenceSystemMatrixProtocol.jl +++ b/src/Protocols/MultiSequenceSystemMatrixProtocol.jl @@ -88,12 +88,12 @@ function _init(protocol::MultiSequenceSystemMatrixProtocol) protocol.systemMeasState.currPos = 1 protocol.systemMeasState.positions = protocol.params.positions - #Prepare Signals + numRxChannels = length(rxChannels(protocol.params.sequences[1])) # kind of hacky, but actual rxChannels for RedPitaya are only set when setupRx is called rxNumSamplingPoints = rxNumSamplesPerPeriod(protocol.params.sequences[1]) numPeriods = acqNumPeriodsPerFrame(protocol.params.sequences[1]) - signals = zeros(Float32, rxNumSamplingPoints, numRxChannels, numPeriods, numTotalFrames) - protocol.systemMeasState.signals = signals + + #= Initialization of signals happens in execute, to handle restoring the protocol=# protocol.systemMeasState.currentSignal = zeros(Float32, rxNumSamplingPoints, numRxChannels, numPeriods, 1) @@ -142,9 +142,14 @@ function initMeasData(protocol::MultiSequenceSystemMatrixProtocol) restore(protocol) end end - # Set signals to zero if we didn't restore + # Initialize Signals if !protocol.restored - signals = mmap!(protocol, "signals.bin", protocol.systemMeasState.signals); + numRxChannels = length(rxChannels(protocol.params.sequences[1])) # kind of hacky, but actual rxChannels for RedPitaya are only set when setupRx is called + rxNumSamplingPoints = rxNumSamplesPerPeriod(protocol.params.sequences[1]) + numPeriods = acqNumPeriodsPerFrame(protocol.params.sequences[1]) + numTotalFrames = length(protocol.systemMeasState.measIsBGFrame) + rm(file(protocol, "signals.bin"), force=true) + signals = mmap!(protocol, "signals.bin", Float32, (rxNumSamplingPoints, numRxChannels, numPeriods, numTotalFrames)) protocol.systemMeasState.signals = signals protocol.systemMeasState.signals[:] .= 0.0 end @@ -238,7 +243,7 @@ function performMeasurement(protocol::MultiSequenceSystemMatrixProtocol) # Prepare calib = protocol.systemMeasState index = calib.currPos - @info "Measurement" index length(calib.positions) + @info "Measurement $index of $(length(calib.positions))" daq = getDAQ(protocol.scanner) sequence = protocol.params.sequences[index] @@ -322,8 +327,6 @@ function asyncConsumer(channel::Channel, protocol::MultiSequenceSystemMatrixProt end function store(protocol::MultiSequenceSystemMatrixProtocol, index) - filename = file(protocol, "meta.toml") - rm(filename, force=true) sysObj = protocol.systemMeasState params = MPIFiles.toDict(sysObj.positions) @@ -337,6 +340,12 @@ function store(protocol::MultiSequenceSystemMatrixProtocol, index) #params["temperatures"] = vec(sysObj.temperatures) params["sequences"] = toDict.(protocol.params.sequences) + filename = file(protocol, "meta.toml") + if isfile(filename) + filename_backup = file(protocol, "meta.toml.backup") + mv(filename, filename_backup, force=true) + end + open(filename, "w") do f TOML.print(f, params) end @@ -344,6 +353,7 @@ function store(protocol::MultiSequenceSystemMatrixProtocol, index) Mmap.sync!(sysObj.signals) #Mmap.sync!(sysObj.drivefield) #Mmap.sync!(sysObj.applied) + rm(filename_backup, force=true) return end diff --git a/src/Protocols/RobotBasedSystemMatrixProtocol.jl b/src/Protocols/RobotBasedSystemMatrixProtocol.jl index a3a77e6d..68d600f9 100644 --- a/src/Protocols/RobotBasedSystemMatrixProtocol.jl +++ b/src/Protocols/RobotBasedSystemMatrixProtocol.jl @@ -82,7 +82,7 @@ function SystemMatrixMeasState() RegularGridPositions([1,1,1],[0.0,0.0,0.0],[0.0,0.0,0.0]), 1, Array{Float32,4}(undef,0,0,0,0), Array{Float32,4}(undef,0,0,0,0), Vector{Bool}(undef,0), Vector{Int64}(undef,0), Vector{Bool}(undef,0), - Matrix{Float64}(undef,0,0), Array{ComplexF64,4}(undef,0,0,0,0), Array{ComplexF64,4}(undef,0,0,0,0)) + Matrix{Float32}(undef,0,0), Array{ComplexF64,4}(undef,0,0,0,0), Array{ComplexF64,4}(undef,0,0,0,0)) end function requiredDevices(protocol::RobotBasedSystemMatrixProtocol) From 50abe1573723bdb116556c36006f8423a741e1de Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Thu, 25 Apr 2024 14:31:25 +0200 Subject: [PATCH 111/168] small fix --- src/Devices/Virtual/TxDAQController.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index 4ee775c5..0a715c95 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -854,10 +854,11 @@ function validateAgainstForwardCalibrationAndSafetyLimit(tx::Matrix{<:Complex}, # step 2 check B_fw against B (rel. and abs. Accuracy) forwardCalibrationAgrees = isapprox.(abs.(B_fw), abs.(B), rtol = txCont.params.fieldToVoltRelDeviation, atol=ustrip(u"T",txCont.params.fieldToVoltAbsDeviation)) - @debug "validateAgainstForwardCalibrationAndSafetyLimit" abs.(B_fw) abs.(B) forwardCalibrationAgrees isSafe(B_fw) isSafe(B) # step 3 check if B_fw and B are both below safety limit isSafe(Btest) = abs.(Btest). Date: Mon, 22 Jul 2024 17:52:03 +0200 Subject: [PATCH 112/168] added function to check if field component is given as a field --- src/Sequences/ContinuousElectricalChannel.jl | 2 ++ src/Sequences/PeriodicElectricalChannel.jl | 2 ++ src/Sequences/StepwiseElectricalChannel.jl | 2 ++ src/Sequences/TxChannel.jl | 3 +++ 4 files changed, 9 insertions(+) diff --git a/src/Sequences/ContinuousElectricalChannel.jl b/src/Sequences/ContinuousElectricalChannel.jl index c19415a8..a1de9874 100644 --- a/src/Sequences/ContinuousElectricalChannel.jl +++ b/src/Sequences/ContinuousElectricalChannel.jl @@ -18,6 +18,8 @@ Base.@kwdef mutable struct ContinuousElectricalChannel <: AcyclicElectricalTxCha waveform::Waveform = WAVEFORM_SINE end +unitIsTesla(chan::ContinuousElectricalChannel) = (dimension(chan.offset) == dimension(u"T")) && (dimension(chan.amplitude)==dimension(u"T")) + channeltype(::Type{<:ContinuousElectricalChannel}) = StepwiseTxChannel() function createFieldChannel(channelID::AbstractString, channelType::Type{ContinuousElectricalChannel}, channelDict::Dict{String, Any}) diff --git a/src/Sequences/PeriodicElectricalChannel.jl b/src/Sequences/PeriodicElectricalChannel.jl index 9790e681..c22b6956 100644 --- a/src/Sequences/PeriodicElectricalChannel.jl +++ b/src/Sequences/PeriodicElectricalChannel.jl @@ -52,6 +52,8 @@ Base.@kwdef mutable struct PeriodicElectricalChannel <: ElectricalTxChannel dcEnabled::Bool = true end +unitIsTesla(chan::PeriodicElectricalChannel) = (dimension(offset(chan)) == dimension(u"T")) && all([dimension(amplitude(comp))==dimension(u"T") for comp in components(chan)]) + # Indexing Interface length(ch::PeriodicElectricalChannel) = length(components(ch)) function getindex(ch::PeriodicElectricalChannel, index::Integer) diff --git a/src/Sequences/StepwiseElectricalChannel.jl b/src/Sequences/StepwiseElectricalChannel.jl index 2b646e5a..2c127847 100644 --- a/src/Sequences/StepwiseElectricalChannel.jl +++ b/src/Sequences/StepwiseElectricalChannel.jl @@ -12,6 +12,8 @@ Base.@kwdef mutable struct StepwiseElectricalChannel <: AcyclicElectricalTxChann enable::Vector{Bool} = Bool[] end +unitIsTesla(chan::StepwiseElectricalChannel) = all(dimension(chan.values) .== [dimension(u"T")]) + channeltype(::Type{<:StepwiseElectricalChannel}) = StepwiseTxChannel() function createFieldChannel(channelID::AbstractString, channelType::Type{StepwiseElectricalChannel}, channelDict::Dict{String, Any}) diff --git a/src/Sequences/TxChannel.jl b/src/Sequences/TxChannel.jl index 9594197d..3966f4c5 100644 --- a/src/Sequences/TxChannel.jl +++ b/src/Sequences/TxChannel.jl @@ -52,6 +52,9 @@ cycleDuration(::T, var) where T <: TxChannel = error("The method has not been im export id id(channel::TxChannel) = channel.id +export unitIsTesla +unitIsTesla(channel::ElectricalTxChannel) = false #default fallback + function toDict!(dict, channel::TxChannel) for field in [x for x in fieldnames((typeof(channel))) if x != :id] dict[String(field)] = toDictValue(getproperty(channel, field)) From 6c9eb6eeeed32f075c1010e37b258c309c531c43 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Mon, 22 Jul 2024 17:54:54 +0200 Subject: [PATCH 113/168] restructured check if sequence can be controlled --- src/Devices/Virtual/TxDAQController.jl | 71 ++++++++++++++------------ 1 file changed, 39 insertions(+), 32 deletions(-) diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index 0a715c95..388ae029 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -61,13 +61,42 @@ end neededDependencies(::TxDAQController) = [AbstractDAQ] optionalDependencies(::TxDAQController) = [SurveillanceUnit, Amplifier, TemperatureController] -function ControlSequence(txCont::TxDAQController, target::Sequence, daq::AbstractDAQ) +function checkIfControlPossible(txCont::TxDAQController, target::Sequence) + daq = dependency(txCont, AbstractDAQ) + + if any(.!isa.(getControlledChannels(target), ElectricalTxChannel)) + error("A field that requires control can only have ElectricalTxChannels.") + end + + if !all(unitIsTesla.(getControlledChannels(target))) + error("All values corresponding to a field that should be controlled need to be given in T, instead of V or A!") + end + + periodicChannels = [channel for channel in getControlledChannels(target) if typeof(channel) <: PeriodicElectricalChannel] + lutChannels = [channel for channel in getControlledChannels(target) if typeof(channel) <: AcyclicElectricalTxChannel] + + if !isempty(lutChannels) #&& !txCont.params.controlDC + error("A field that requires control can only have PeriodicElectricalChannels if controlDC is set to false for the TxDAQController!") + end - if any(.!isa.(getControlledChannels(target), PeriodicElectricalChannel)) - error("A field that requires control can only have PeriodicElectricalChannels.") # TODO/JA: check if this limitation can be lifted: would require special care when merging a sequence + outputsPeriodic = channelIdx(daq, id.(periodicChannels)) + if !allunique(outputsPeriodic) + error("Multiple periodic field channels are output on the same DAC output. This can not be controlled!") end - currSeq = prepareSequenceForControl(target) + mapToFastDAC(i) = begin x = mod(i,6); if x==1 || x==2; return 2*(i÷6)+x end end + outputsLUT = channelIdx(daq, id.(lutChannels)) + if !all([x ∈ outputsPeriodic for x = mapToFastDAC.(outputsLUT)]) + error("If AcyclicElectricalTxChannels should be controlled they must map to the same output as a controlled PeriodicElectricalChannel!") + end + + +end + +function ControlSequence(txCont::TxDAQController, target::Sequence) + daq = dependency(txCont, AbstractDAQ) + + currSeq = prepareSequenceForControl(txCont, target) measuredFrames = max(cld(txCont.params.minimumStepDuration, ustrip(u"s", dfCycle(currSeq))),1) discardedFrames = cld(txCont.params.timeUntilStable, ustrip(u"s", dfCycle(currSeq))) @@ -226,43 +255,21 @@ function createReferenceIndexMapping(controlledChannelsDict::OrderedDict{Periodi return [mapping[x] for x in controlOrderChannelIndices] end -function prepareSequenceForControl(seq::Sequence) +function prepareSequenceForControl(txCont::TxDAQController, seq::Sequence) # Ausgangslage: Eine Sequenz enthält eine Reihe an Feldern, von denen ggf nicht alle geregelt werden sollen # Jedes dieser Felder enthält eine Reihe an Channels, die nicht alle geregelt werden können (nur periodicElectricalChannel) # Ziel: Eine Sequenz, die, wenn Sie abgespielt wird, alle Informationen beinhaltet, die benötigt werden, um die Regelung durchzuführen, Sendefelder in V - + checkIfControlPossible(txCont, seq) + _name = "Control Sequence for target $(name(seq))" description = "" _targetScanner = targetScanner(seq) _baseFrequency = baseFrequency(seq) general = GeneralSettings(;name=_name, description = description, targetScanner = _targetScanner, baseFrequency = _baseFrequency) acq = AcquisitionSettings(;channels = RxChannel[], bandwidth = rxBandwidth(seq)) # uses the default values of 1 for numPeriodsPerFrame, numFrames, numAverages, numFrameAverages - - _fields = MagneticField[] - for field in fields(seq) - if control(field) - _id = id(field) - safeStart = safeStartInterval(field) - safeTrans = safeTransitionInterval(field) - safeEnd = safeEndInterval(field) - safeError = safeErrorInterval(field) - # Use only periodic electrical channels - periodicChannel = [deepcopy(channel) for channel in periodicElectricalTxChannels(field)] - #periodicComponents = [comp for channel in periodicChannel for comp in periodicElectricalComponents(channel)] - for channel in periodicChannel - for comp in components(channel) - if dimension(amplitude(comp)) != dimension(1.0u"T") - error("The amplitude components of a field that is controlled by a TxDAQController need to be given in T. Please fix component $(id(comp)) of channel $(id(channel))") - end - end - end - contField = MagneticField(;id = _id, channels = periodicChannel, safeStartInterval = safeStart, safeTransitionInterval = safeTrans, - safeEndInterval = safeEnd, safeErrorInterval = safeError, decouple = decouple(field), control = true) - push!(_fields, contField) - end - end - return Sequence(;general = general, acquisition = acq, fields = _fields) + + return Sequence(;general = general, acquisition = acq, fields = [deepcopy(f) for f in fields(seq) if control(f)]) end @@ -297,7 +304,7 @@ function controlTx(txCont::TxDAQController, seq::Sequence, ::Nothing = nothing) if needsControlOrDecoupling(seq) daq = dependency(txCont, AbstractDAQ) setupRx(daq, seq) - control = ControlSequence(txCont, seq, daq) # depending on the controlled channels and settings this will select the appropiate type of ControlSequence + control = ControlSequence(txCont, seq) # depending on the controlled channels and settings this will select the appropiate type of ControlSequence return controlTx(txCont, control) else @warn "The sequence you selected does not need control, even though the protocol wanted to control!" From 4365cda0e350efcb16186a67f9d14875003b47a5 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Mon, 22 Jul 2024 17:55:30 +0200 Subject: [PATCH 114/168] prepare caching of control results --- src/Devices/DAQ/DAQ.jl | 39 ++++++++++++++++---------- src/Devices/Virtual/TxDAQController.jl | 19 +++++++++++++ 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/src/Devices/DAQ/DAQ.jl b/src/Devices/DAQ/DAQ.jl index 99aa2332..23220415 100644 --- a/src/Devices/DAQ/DAQ.jl +++ b/src/Devices/DAQ/DAQ.jl @@ -269,23 +269,32 @@ function calibration(daq::AbstractDAQ, channelID::AbstractString) return channel(daq, channelID).calibration end end - + +function calibration(daq::AbstractDAQ, channelID::AbstractString, frequency::Real) + cal = calibration(daq, channelID) + if cal isa TransferFunction + return cal(frequency) + else + @warn "You requested a calibration for a specific frequency $frequency but the channel $channelID has no frequency dependent calibration value" + return cal + end +end export applyForwardCalibration!, applyForwardCalibration -function applyForwardCalibration(seq::Sequence, daq::AbstractDAQ) +function applyForwardCalibration(seq::Sequence, device::Device) seqCopy = deepcopy(seq) - applyForwardCalibration!(seqCopy, daq) + applyForwardCalibration!(seqCopy, device) return seqCopy end -function applyForwardCalibration!(seq::Sequence, daq::AbstractDAQ) +function applyForwardCalibration!(seq::Sequence, device::Device) for channel in periodicElectricalTxChannels(seq) off = offset(channel) if dimension(off) != dimension(1.0u"V") - isnothing(calibration(daq, id(channel))) && throw(ScannerConfigurationError("An offset value in channel $(id(channel)) requires calibration but no calibration is configured on the DAQ channel!")) - offsetVolts = off*abs(calibration(daq, id(channel))(0)) # use DC value for offsets + isnothing(calibration(device, id(channel))) && throw(ScannerConfigurationError("An offset value in channel $(id(channel)) requires calibration but no calibration is configured on the DAQ channel!")) + offsetVolts = off*abs(calibration(device, id(channel), 0)) # use DC value for offsets offset!(channel, uconvert(u"V",offsetVolts)) end @@ -293,15 +302,15 @@ function applyForwardCalibration!(seq::Sequence, daq::AbstractDAQ) amp = amplitude(comp) pha = phase(comp) if dimension(amp) != dimension(1.0u"V") - isnothing(calibration(daq, id(channel))) && throw(ScannerConfigurationError("An amplitude value in channel $(id(channel)) requires calibration but no calibration is configured on the DAQ channel!")) + isnothing(calibration(device, id(channel))) && throw(ScannerConfigurationError("An amplitude value in channel $(id(channel)) requires calibration but no calibration is configured on the DAQ channel!")) f_comp = ustrip(u"Hz", txBaseFrequency(seq)) / divider(comp) - complex_comp = (amp*exp(im*pha)) * calibration(daq, id(channel))(f_comp) + complex_comp = (amp*exp(im*pha)) * calibration(device, id(channel), f_comp) amplitude!(comp, uconvert(u"V",abs(complex_comp))) phase!(comp, angle(complex_comp)u"rad") if comp isa ArbitraryElectricalComponent N = length(values(comp)) f_awg = rfftfreq(N, f_comp*N) - calib = calibration(daq, id(channel))(f_awg) ./ (abs.(calibration(daq, id(channel))(f_comp))*exp.(im*2*pi*range(0,length(f_awg)-1).*angle(calibration(daq, id(channel))(f_comp)))) # since amplitude and phase are already calibrated for the base frequency, here we need to remove that factor + calib = calibration(device, id(channel), f_awg) ./ (abs.(calibration(device, id(channel), f_comp))*exp.(im*2*pi*range(0,length(f_awg)-1).*angle(calibration(device, id(channel),f_comp)))) # since amplitude and phase are already calibrated for the base frequency, here we need to remove that factor values!(comp, irfft(rfft(values(comp)).*calib, N)) end end @@ -312,21 +321,21 @@ function applyForwardCalibration!(seq::Sequence, daq::AbstractDAQ) if lutChannel isa StepwiseElectricalChannel values = lutChannel.values if dimension(values[1]) != dimension(1.0u"V") - isnothing(calibration(daq, id(lutChannel))) && throw(ScannerConfigurationError("A value in channel $(id(lutChannel)) requires calibration but no calibration is configured on the DAQ channel!")) - values = values.*calibration(daq, id(lutChannel)) + isnothing(calibration(device, id(lutChannel))) && throw(ScannerConfigurationError("A value in channel $(id(lutChannel)) requires calibration but no calibration is configured on the DAQ channel!")) + values = values.*calibration(device, id(lutChannel)) lutChannel.values = values end elseif lutChannel isa ContinuousElectricalChannel amp = lutChannel.amplitude off = lutChannel.offset if dimension(amp) != dimension(1.0u"V") - isnothing(calibration(daq, id(lutChannel))) && throw(ScannerConfigurationError("An amplitude value in channel $(id(lutChannel)) requires calibration but no calibration is configured on the DAQ channel!")) - amp = amp*calibration(daq, id(lutChannel)) + isnothing(calibration(device, id(lutChannel))) && throw(ScannerConfigurationError("An amplitude value in channel $(id(lutChannel)) requires calibration but no calibration is configured on the DAQ channel!")) + amp = amp*calibration(device, id(lutChannel)) lutChannel.amplitude = amp end if dimension(off) != dimension(1.0u"V") - isnothing(calibration(daq, id(lutChannel))) && throw(ScannerConfigurationError("An offset value in channel $(id(lutChannel)) requires calibration but no calibration is configured on the DAQ channel!")) - off = off*calibration(daq, id(lutChannel)) + isnothing(calibration(device, id(lutChannel))) && throw(ScannerConfigurationError("An offset value in channel $(id(lutChannel)) requires calibration but no calibration is configured on the DAQ channel!")) + off = off*calibration(device, id(lutChannel)) lutChannel.offset = off end end diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index 388ae029..3ca2e68a 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -38,6 +38,7 @@ mutable struct AWControlSequence <: ControlSequence refIndices::Vector{Int64} rfftIndices::BitArray{3} # Matrix of size length(controlledChannelsDict) x 4 (max. num of components) x len(rfft) dcSearch::Vector{@NamedTuple{V::Vector{Float64}, B::Vector{Float64}}} + #lutMap::Dict{String, Dict{AcyclicElectricalTxChannel, Int}} # for every field ID contain a dict of removed LUTChannels together with the corresponding channel end @enum ControlResult UNCHANGED UPDATED INVALID @@ -48,6 +49,23 @@ Base.@kwdef mutable struct TxDAQController <: VirtualDevice ref::Union{Array{Float32, 4}, Nothing} = nothing # TODO remove when done cont::Union{Nothing, ControlSequence} = nothing # TODO remove when done startFrame::Int64 = 1 + controlResults::Dict{String, Union{typeof(1.0u"V/T"), Dict{Float64,typeof(1.0u"V/T")}}} = Dict{String, Union{typeof(1.0u"V/T"), Dict{Float64,typeof(1.0u"V/T")}}}() +end + +function calibration(txCont::TxDAQController, channelID::AbstractString) + if haskey(txCont.controlResults, channelID) && txCont.controlResults[channelID] isa typeof(1.0u"V/T") + return txCont.controlResults[channelID] + else + return calibration(dependency(txCont, AbstractDAQ), channelID) + end +end + +function calibration(txCont::TxDAQController, channelID::AbstractString, frequency::Real) + if haskey(txCont.controlResults, channelID) && txCont.controlResults[channelID] isa Dict && haskey(txCont.controlResults[channelID],frequency) + return txCont.controlResults[channelID][frequency] + else + return calibration(dependency(txCont, AbstractDAQ), channelID, frequency) + end end function _init(tx::TxDAQController) @@ -405,6 +423,7 @@ function controlTx(txCont::TxDAQController, control::ControlSequence) controlPhaseDone = controlStep!(control, txCont, Γ, Ω) == UNCHANGED if controlPhaseDone @info "Could control" + # TODO/JA: extract control results as new calibration here else @info "Could not control" end From 367a184755fc915895ba8d5104d06dc856b90787 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Mon, 9 Sep 2024 16:03:05 +0200 Subject: [PATCH 115/168] needed for new versions of rationalize https://github.com/JuliaLang/julia/pull/43427 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 86d7859c..ef27e5e9 100644 --- a/Project.toml +++ b/Project.toml @@ -58,7 +58,7 @@ TOML = "1" ThreadPools = "2.1.1" UUIDs = "1" Unitful = "1.13, 1.14, 1.15, 1.16, 1.17" -julia = "1.7" +julia = "1.10" [extras] Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" From b025e9f3ea47dd9396a32db33435313e1062db53 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Mon, 9 Sep 2024 16:04:01 +0200 Subject: [PATCH 116/168] fix backup filename --- src/Protocols/MultiSequenceSystemMatrixProtocol.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Protocols/MultiSequenceSystemMatrixProtocol.jl b/src/Protocols/MultiSequenceSystemMatrixProtocol.jl index 55531aee..8a4efa02 100644 --- a/src/Protocols/MultiSequenceSystemMatrixProtocol.jl +++ b/src/Protocols/MultiSequenceSystemMatrixProtocol.jl @@ -341,8 +341,8 @@ function store(protocol::MultiSequenceSystemMatrixProtocol, index) params["sequences"] = toDict.(protocol.params.sequences) filename = file(protocol, "meta.toml") + filename_backup = file(protocol, "meta.toml.backup") if isfile(filename) - filename_backup = file(protocol, "meta.toml.backup") mv(filename, filename_backup, force=true) end From ddf4136fc5eb80b9507eddafd1f01323bba0b039 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Mon, 9 Sep 2024 16:04:39 +0200 Subject: [PATCH 117/168] add caching of control results --- src/Devices/Virtual/TxDAQController.jl | 48 +++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index c574b49c..0f1964e8 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -49,7 +49,9 @@ Base.@kwdef mutable struct TxDAQController <: VirtualDevice ref::Union{Array{Float32, 4}, Nothing} = nothing # TODO remove when done cont::Union{Nothing, ControlSequence} = nothing # TODO remove when done startFrame::Int64 = 1 - controlResults::Dict{String, Union{typeof(1.0u"V/T"), Dict{Float64,typeof(1.0u"V/T")}}} = Dict{String, Union{typeof(1.0u"V/T"), Dict{Float64,typeof(1.0u"V/T")}}}() + controlResults::OrderedDict{String, Union{typeof(1.0im*u"V/T"), Dict{Float64,typeof(1.0im*u"V/T")}}} = Dict{String, Union{typeof(1.0im*u"V/T"), Dict{Float64,typeof(1.0im*u"V/T")}}}() + lastDCResults::Union{Vector{@NamedTuple{V::Vector{Float64}, B::Vector{Float64}}},Nothing} = nothing + lastChannelIDs::Vector{String} = String[] end function calibration(txCont::TxDAQController, channelID::AbstractString) @@ -121,9 +123,7 @@ function ControlSequence(txCont::TxDAQController, target::Sequence) txCont.startFrame = discardedFrames + 1 acqNumFrames(currSeq, discardedFrames+measuredFrames) - - - applyForwardCalibration!(currSeq, daq) # uses the forward calibration to convert the values for the field from T to V + applyForwardCalibration!(currSeq, txCont) # uses the forward calibration to convert the values for the field from T to V seqControlledChannels = getControlledChannels(currSeq) @@ -164,8 +164,22 @@ function ControlSequence(txCont::TxDAQController, target::Sequence) end rfftIndices = createRFFTindices(controlledChannelsDict, target, daq) + + cont = AWControlSequence(target, currSeq, controlledChannelsDict, refIndices, rfftIndices, []) + + ## Apply last DC result + if !isnothing(txCont.lastDCResults) && (txCont.lastChannelIDs == id.(seqControlledChannels)) + cont.dcSearch = txCont.lastDCResults[end-1:end] + Ω = calcDesiredField(cont) + initTx = calcControlMatrix(cont) + last = cont.dcSearch[end] + previous = cont.dcSearch[end-1] + initTx[:,1] .= previous.V .- ((previous.B.-Ω[:,1]).*(last.V.-previous.V))./(last.B.-previous.B) + @info "Would have reused last DC Results" initTx[:,1] + #updateControlSequence!(cont, initTx) + end - return AWControlSequence(target, currSeq, controlledChannelsDict, refIndices, rfftIndices, []) + return cont end end @@ -424,6 +438,7 @@ function controlTx(txCont::TxDAQController, control::ControlSequence) if controlPhaseDone @info "Could control" # TODO/JA: extract control results as new calibration here + updateCachedCalibration(txCont, control) else @info "Could not control" end @@ -515,6 +530,29 @@ end setup(daq::AbstractDAQ, sequence::ControlSequence) = setup(daq, getControlResult(sequence)) +function updateCachedCalibration(txCont::TxDAQController, cont::AWControlSequence) + finalCalibration = calcControlMatrix(cont) ./ calcDesiredField(cont) + + calibrationResults = findall(x->!isnan(x), finalCalibration) + channelIDs = id.(keys(cont.controlledChannelsDict)) + freqAxis = rfftfreq(rxNumSamplingPoints(cont.currSequence),ustrip(u"Hz",2*rxBandwidth(cont.currSequence))) + + for res in calibrationResults + chId = channelIDs[res[1]] + f = freqAxis[res[2]] + if !haskey(txCont.controlResults, chId) + txCont.controlResults[chId] = Dict{Float64,typeof(1.0im*u"V/T")}() + end + txCont.controlResults[chId][f] = finalCalibration[res]*u"V/T" + @debug "Cached calibration result:" chId f finalCalibration[res] + end + + txCont.lastDCResults = cont.dcSearch[end-1:end] + txCont.lastChannelIDs = channelIDs + + @info "Cached DC result" txCont.lastDCResults +end + # TODO/JA: check if changes here needs changes somewhere else getControlledFields(seq::Sequence) = [field for field in seq.fields if field.control] From c1beb6115f8d3be06a02c12623eccba9abc78ef2 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Tue, 10 Sep 2024 16:40:27 +0200 Subject: [PATCH 118/168] fix Aqua --- Project.toml | 4 +++- src/Sequences/MagneticField.jl | 2 +- src/Utils/MmapFiles.jl | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Project.toml b/Project.toml index ef27e5e9..2adf7d24 100644 --- a/Project.toml +++ b/Project.toml @@ -35,11 +35,12 @@ Aqua = "0.8" DataStructures = "0.18" Dates = "1" DocStringExtensions = "0.8, 0.9" +FFTW = "1.8" HDF5 = "0.16" InteractiveUtils = "1" LibSerialPort = "0.5.2" LinearAlgebra = "1" -MPIFiles = "0.15" +MPIFiles = "0.15,0.16" MacroTools = "0.5.6" Mmap = "1" NaturalSort = "1" @@ -58,6 +59,7 @@ TOML = "1" ThreadPools = "2.1.1" UUIDs = "1" Unitful = "1.13, 1.14, 1.15, 1.16, 1.17" +UnicodePlots = "3" julia = "1.10" [extras] diff --git a/src/Sequences/MagneticField.jl b/src/Sequences/MagneticField.jl index 2aead9f9..1c5aa420 100644 --- a/src/Sequences/MagneticField.jl +++ b/src/Sequences/MagneticField.jl @@ -109,7 +109,7 @@ periodicElectricalTxChannels(field::MagneticField) = channels(field, PeriodicEle export acyclicElectricalTxChannels acyclicElectricalTxChannels(field::MagneticField) = channels(field, AcyclicElectricalTxChannel) -export protocolElectricalTxChannels +export protocolTxChannels protocolTxChannels(field::MagneticField) = channels(field, ProtocolTxChannel) function toDict!(dict, field::MagneticField) diff --git a/src/Utils/MmapFiles.jl b/src/Utils/MmapFiles.jl index a1730686..58483e8b 100644 --- a/src/Utils/MmapFiles.jl +++ b/src/Utils/MmapFiles.jl @@ -14,7 +14,7 @@ function mmap!(protocol::Protocol, f::String, array::Array{T,N}) where {T,N} return mmap(protocol, f, T) end -function mmap!(protocol::Protocol, f::String, eltype::DataType, size::Union{NTuple{N, Int64}, Vector{Int64}}) where N +function mmap!(protocol::Protocol, f::String, eltype::DataType, size::Union{Tuple{Vararg{Int64}}, Vector{Int64}}) open(file(protocol, f), "w+") do io write(io, length(size)) for s in size From cb5c5cf60feb7a96813a7e0eeea69a188457c6c4 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Wed, 18 Sep 2024 09:29:13 +0200 Subject: [PATCH 119/168] TF moved from scanner to individual RX channels --- Project.toml | 2 +- src/Devices/DAQ/DAQ.jl | 106 ++++++++++++--------- src/Devices/DAQ/RedPitayaDAQ.jl | 7 +- src/Devices/DAQ/SimpleSimulatedDAQ.jl | 5 +- src/Devices/Virtual/TxDAQController.jl | 80 +++++++--------- src/Protocols/Storage/MDF.jl | 15 ++- src/Scanner.jl | 10 -- src/Sequences/PeriodicElectricalChannel.jl | 2 +- src/Utils/DictToStruct.jl | 10 ++ 9 files changed, 120 insertions(+), 117 deletions(-) diff --git a/Project.toml b/Project.toml index 2adf7d24..19fe52fb 100644 --- a/Project.toml +++ b/Project.toml @@ -48,7 +48,7 @@ Pkg = "1" Plots = "1.39" ProgressMeter = "1.5" REPL = "1" -RedPitayaDAQServer = "0.6, 0.7" +RedPitayaDAQServer = "0.6, 0.7, 0.8" ReplMaker = "0.2.7" Scratch = "1.1.0" Sockets = "1" diff --git a/src/Devices/DAQ/DAQ.jl b/src/Devices/DAQ/DAQ.jl index 23220415..2fe53a34 100644 --- a/src/Devices/DAQ/DAQ.jl +++ b/src/Devices/DAQ/DAQ.jl @@ -1,11 +1,11 @@ import Base: setindex!, getindex -export AbstractDAQ, DAQParams, SinkImpedance, SINK_FIFTY_OHM, SINK_HIGH, DAQTxChannelSettings, DAQChannelParams, DAQFeedback, DAQTxChannelParams, DAQRxChannelParams, +export AbstractDAQ, DAQParams, SinkImpedance, SINK_FIFTY_OHM, SINK_HIGH, DAQTxChannelSettings, DAQChannelParams, DAQTxChannelParams, DAQRxChannelParams, createDAQChannels, createDAQParams, startTx, stopTx, setTxParams, readData, numRxChannelsTotal, numTxChannelsTotal, numRxChannelsActive, numTxChannelsActive, currentPeriod, getDAQ, getDAQs, channelIdx, limitPeak, sinkImpedance, allowedWaveforms, isWaveformAllowed, - feedbackChannelID, feedbackCalibration, calibration + feedbackChannelID, feedbackTransferFunction, transferFunction, hasTransferFunction, calibration abstract type AbstractDAQ <: Device end abstract type DAQParams <: DeviceParams end @@ -57,11 +57,6 @@ abstract type DAQChannelParams end abstract type TxChannelParams <: DAQChannelParams end abstract type RxChannelParams <: DAQChannelParams end -Base.@kwdef mutable struct DAQFeedback - channelID::AbstractString - calibration::Union{TransferFunction, String, Nothing} = nothing -end - Base.@kwdef struct DAQHBridge{N} channelID::Union{String, Vector{String}} manual::Bool = false @@ -97,12 +92,13 @@ Base.@kwdef mutable struct DAQTxChannelParams <: TxChannelParams limitSlewRate::typeof(1.0u"V/s") = 1000.0u"V/µs" # default is basically no limit sinkImpedance::SinkImpedance = SINK_HIGH allowedWaveforms::Vector{Waveform} = [WAVEFORM_SINE] - feedback::Union{DAQFeedback, Nothing} = nothing + feedbackChannelID::Union{String, Nothing} = nothing calibration::Union{TransferFunction, String, Nothing} = nothing end -Base.@kwdef struct DAQRxChannelParams <: RxChannelParams +Base.@kwdef mutable struct DAQRxChannelParams <: RxChannelParams channelIdx::Int64 + transferFunction::Union{TransferFunction, String, Nothing} = nothing end function createDAQChannel(::Type{DAQTxChannelParams}, dict::Dict{String,Any}) @@ -122,10 +118,8 @@ function createDAQChannel(::Type{DAQTxChannelParams}, dict::Dict{String,Any}) splattingDict[:allowedWaveforms] = toWaveform.(dict["allowedWaveforms"]) end - if haskey(dict, "feedback") - channelID = dict["feedback"]["channelID"] - calibration_tf = parse_into_tf(dict["feedback"]["calibration"]) - splattingDict[:feedback] = DAQFeedback(channelID=channelID, calibration=calibration_tf) + if haskey(dict, "feedbackChannelID") + splattingDict[:feedbackChannelID] = dict["feedbackChannelID"] end if haskey(dict, "calibration") @@ -135,17 +129,16 @@ function createDAQChannel(::Type{DAQTxChannelParams}, dict::Dict{String,Any}) return DAQTxChannelParams(;splattingDict...) end -function parse_into_tf(value::String) - if occursin(".h5", value) # case 1: filename to transfer function, the TF will be read the first time calibration() is called, (done in _init(), to prevent delays while using the device) - calibration_tf = value - else # case 2: single value, extended into transfer function with no frequency dependency - calibration_value = uparse(value) - calibration_tf = TransferFunction([0,10e6],ComplexF64[ustrip(calibration_value), ustrip(calibration_value)], units=[unit(calibration_value)]) +function createDAQChannel(::Type{DAQRxChannelParams}, dict::Dict{String,Any}) + splattingDict = Dict{Symbol, Any}() + splattingDict[:channelIdx] = dict["channel"] + + if haskey(dict, "transferFunction") + splattingDict[:transferFunction] = parse_into_tf(dict["transferFunction"]) end - return calibration_tf -end -createDAQChannel(::Type{DAQRxChannelParams}, value) = DAQRxChannelParams(channelIdx=value["channel"]) + return DAQRxChannelParams(;splattingDict...) +end "Create DAQ channel description from device dict part." function createDAQChannels(dict::Dict{String, Any}) @@ -231,6 +224,7 @@ end @mustimplement numComponentsMax(daq::AbstractDAQ) @mustimplement canPostpone(daq::AbstractDAQ) @mustimplement canConvolute(daq::AbstractDAQ) +@mustimplement channel(daq::AbstractDAQ, channelID::AbstractString) getDAQs(scanner::MPIScanner) = getDevices(scanner, AbstractDAQ) getDAQ(scanner::MPIScanner) = getDevice(scanner, AbstractDAQ) @@ -242,36 +236,57 @@ getDAQ(scanner::MPIScanner) = getDevice(scanner, AbstractDAQ) # return factor # end -channel(daq::AbstractDAQ, channelID::AbstractString) = daq.params.channels[channelID] -channelIdx(daq::AbstractDAQ, channelID::AbstractString) = channel(daq, channelID).channelIdx -channelIdx(daq::AbstractDAQ, channelIDs::Vector{<:AbstractString}) = [channel(daq, channelID).channelIdx for channelID in channelIDs] -limitPeak(daq::AbstractDAQ, channelID::AbstractString) = channel(daq, channelID).limitPeak -sinkImpedance(daq::AbstractDAQ, channelID::AbstractString) = channel(daq, channelID).sinkImpedance -allowedWaveforms(daq::AbstractDAQ, channelID::AbstractString) = channel(daq, channelID).allowedWaveforms -isWaveformAllowed(daq::AbstractDAQ, channelID::AbstractString, waveform::Waveform) = waveform in allowedWaveforms(daq, channelID) -feedback(daq::AbstractDAQ, channelID::AbstractString) = channel(daq, channelID).feedback -feedbackChannelID(daq::AbstractDAQ, channelID::AbstractString) = feedback(daq, channelID).channelID -function feedbackCalibration(daq::AbstractDAQ, channelID::AbstractString) - if !isnothing(feedback(daq, channelID)) && isa(feedback(daq, channelID).calibration, String) # if TF has not been loaded yet, load the h5 file - feedback(daq, channelID).calibration = TransferFunction(joinpath(configDir(daq),"TransferFunctions",feedback(daq, channelID).calibration)) +channelIdx(channel::DAQChannelParams) = channel.channelIdx +channelIdx(daq::AbstractDAQ, channelID::AbstractString) = channelIdx(channel(daq, channelID)) +channelIdx(daq::AbstractDAQ, channelIDs::Vector{<:AbstractString}) = [channelIdx(channel(daq, channelID)) for channelID in channelIDs] + +limitPeak(channel::DAQTxChannelParams) = channel.limitPeak +limitPeak(daq::AbstractDAQ, channelID::AbstractString) = limitPeak(channel(daq, channelID)) + +sinkImpedance(channel::DAQTxChannelParams) = channel.sinkImpedance +sinkImpedance(daq::AbstractDAQ, channelID::AbstractString) = sinkImpedance(channel(daq, channelID)) + +allowedWaveforms(channel::DAQTxChannelParams) = channel.allowedWaveforms +allowedWaveforms(daq::AbstractDAQ, channelID::AbstractString) = allowedWaveforms(channel(daq, channelID)) + +isWaveformAllowed(channel::DAQTxChannelParams, waveform::Waveform) = waveform in allowedWaveforms(channel) +isWaveformAllowed(daq::AbstractDAQ, channelID::AbstractString, waveform::Waveform) = isWaveformAllowed(channel(daq, channelID), waveform) + +feedbackChannelID(channel::DAQTxChannelParams) = channel.feedbackChannelID +feedbackChannelID(daq::AbstractDAQ, channelID::AbstractString) = feedbackChannelID(channel(daq, channelID)) + +feedbackChannel(daq::AbstractDAQ, channel_::DAQTxChannelParams) = channel(daq, feedbackChannelID(channel_)) +feedbackChannel(daq::AbstractDAQ, channelID::AbstractString) = feedbackChannel(daq, channel(daq,channelID)) + +feedbackTransferFunction(daq::AbstractDAQ, channel::DAQTxChannelParams) = transferFunction(daq, feedbackChannel(daq, channel)) +feedbackTransferFunction(daq::AbstractDAQ, channelID::AbstractString) = feedbackTransferFunction(daq, channel(daq, channelID)) + +hasTransferFunction(channel::DAQRxChannelParams) = !isnothing(channel.transferFunction) +hasTransferFunction(daq::AbstractDAQ, channelID::AbstractString) = hasTransferFunction(channel(daq,channelID)) + +transferFunction(::AbstractDAQ, ::Nothing) = nothing +transferFunction(daq::AbstractDAQ, channelID::AbstractString) = transferFunction(daq, channel(daq, channelID)) +function transferFunction(dev::Union{MPIScanner, AbstractDAQ}, channel::DAQRxChannelParams) + if isa(channel.transferFunction, String) + channel.transferFunction = TransferFunction(joinpath(configDir(dev), "TransferFunctions", channel.transferFunction)) else - if !isnothing(feedback(daq, channelID)) - return feedback(daq, channelID).calibration - else - return nothing - end + channel.transferFunction end end -function calibration(daq::AbstractDAQ, channelID::AbstractString) - if isa(channel(daq, channelID).calibration, String) # if TF has not been loaded yet, load the h5 file - channel(daq, channelID).calibration = TransferFunction(joinpath(configDir(daq),"TransferFunctions",channel(daq, channelID).calibration)) + +calibration(daq::AbstractDAQ, channelID::AbstractString) = calibration(daq, channel(daq,channelID)) +function calibration(dev::Union{MPIScanner, AbstractDAQ}, channel::DAQTxChannelParams) + if isa(channel.calibration, String) + channel.calibration = TransferFunction(joinpath(configDir(dev), "TransferFunctions", channel.calibration)) else - return channel(daq, channelID).calibration + channel.calibration end end -function calibration(daq::AbstractDAQ, channelID::AbstractString, frequency::Real) - cal = calibration(daq, channelID) +calibration(dev::Device, channelID::AbstractString, frequencies) = calibration.([dev], [channelID], frequencies) +calibration(daq::AbstractDAQ, channelID::AbstractString, frequency::Real) = calibration(daq, channel(daq, channelID), frequency) +function calibration(dev::Union{MPIScanner, AbstractDAQ}, channel::DAQTxChannelParams, frequency::Real) + cal = calibration(dev, channel) if cal isa TransferFunction return cal(frequency) else @@ -311,6 +326,7 @@ function applyForwardCalibration!(seq::Sequence, device::Device) N = length(values(comp)) f_awg = rfftfreq(N, f_comp*N) calib = calibration(device, id(channel), f_awg) ./ (abs.(calibration(device, id(channel), f_comp))*exp.(im*2*pi*range(0,length(f_awg)-1).*angle(calibration(device, id(channel),f_comp)))) # since amplitude and phase are already calibrated for the base frequency, here we need to remove that factor + calib = ustrip.(NoUnits, calib) values!(comp, irfft(rfft(values(comp)).*calib, N)) end end diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index 35a31cba..d2594914 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -102,7 +102,8 @@ function _init(daq::RedPitayaDAQ) for channelID in keys(daq.params.channels) if isa(channel(daq, channelID), DAQTxChannelParams) calibration(daq, channelID) - feedbackCalibration(daq, channelID) + elseif isa(channel(daq, channelID), DAQRxChannelParams) + transferFunction(daq, channelID) end end @@ -148,6 +149,8 @@ optionalDependencies(::RedPitayaDAQ) = [TxDAQController, SurveillanceUnit] Base.close(daq::RedPitayaDAQ) = daq.rpc +channel(daq::RedPitayaDAQ, channelID::AbstractString) = daq.params.channels[channelID] + export usesCounterTrigger usesCounterTrigger(daq::RedPitayaDAQ) = daq.params.useCounterTrigger @@ -1069,7 +1072,7 @@ function setupRx(daq::RedPitayaDAQ, sequence::Sequence) # TODO possibly move some of this into abstract daq daq.refChanIDs = [] txChannels = [channel[2] for channel in daq.params.channels if channel[2] isa DAQTxChannelParams] - daq.refChanIDs = unique([tx.feedback.channelID for tx in txChannels if !isnothing(tx.feedback)]) + daq.refChanIDs = unique(filter!(!isnothing, feedbackChannelID.(txChannels))) # Construct view to save bandwidth rxIDs = sort(union(channelIdx(daq, daq.rxChanIDs), channelIdx(daq, daq.refChanIDs))) diff --git a/src/Devices/DAQ/SimpleSimulatedDAQ.jl b/src/Devices/DAQ/SimpleSimulatedDAQ.jl index f4e30be3..e81f1eef 100644 --- a/src/Devices/DAQ/SimpleSimulatedDAQ.jl +++ b/src/Devices/DAQ/SimpleSimulatedDAQ.jl @@ -54,6 +54,7 @@ end neededDependencies(::SimpleSimulatedDAQ) = [SimulationController] optionalDependencies(::SimpleSimulatedDAQ) = [TxDAQController, SurveillanceUnit] +channel(daq::SimpleSimulatedDAQ, channelID::AbstractString) = daq.params.channels[channelID] Base.close(daq::SimpleSimulatedDAQ) = nothing @@ -90,8 +91,8 @@ function setupTx(daq::SimpleSimulatedDAQ, sequence::Sequence) channelMapping = scannerChannel.channelIdx # Switch to getter? # Activate corresponding receive channels - if !isnothing(scannerChannel.feedback) - feedbackChannelID = scannerChannel.feedback.channelID + if !isnothing(scannerChannel.feedbackChannelID) + feedbackChannelID = scannerChannel.feedbackChannelID scannerFeedbackChannel = daq.params.channels[feedbackChannelID] feedbackChannelIdx = scannerFeedbackChannel.channelIdx # Switch to getter? push!(daq.refChanIDs, feedbackChannelID) diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index 0f1964e8..621a49c8 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -20,22 +20,29 @@ struct UnsortedRef end abstract type ControlSequence end -mutable struct CrossCouplingControlSequence <: ControlSequence +macro ControlSequence_fields() + return esc(quote + #"Goal sequence of the controller, amplitudes are in T" targetSequence::Sequence + #"Current best guess of a sequence to reach the goal, amplitudes are in V" currSequence::Sequence - # Periodic Electric Components + #"Dictionary containing the sequence channels needing control and their respective DAQ channels" controlledChannelsDict::OrderedDict{PeriodicElectricalChannel, TxChannelParams} + #"Vector of indices into the receive channels selecting the correct feedback channels for the controlledChannels" + refIndices::Vector{Int64} + #"Vector of TransferFunctions to be applied to the feedback channels" + refTFs::Vector{TransferFunction} + end) +end + +mutable struct CrossCouplingControlSequence <: ControlSequence + @ControlSequence_fields sinLUT::Union{Matrix{Float64}, Nothing} cosLUT::Union{Matrix{Float64}, Nothing} - refIndices::Vector{Int64} end mutable struct AWControlSequence <: ControlSequence - targetSequence::Sequence - currSequence::Sequence - # Periodic Electric Components - controlledChannelsDict::OrderedDict{PeriodicElectricalChannel, TxChannelParams} - refIndices::Vector{Int64} + @ControlSequence_fields rfftIndices::BitArray{3} # Matrix of size length(controlledChannelsDict) x 4 (max. num of components) x len(rfft) dcSearch::Vector{@NamedTuple{V::Vector{Float64}, B::Vector{Float64}}} #lutMap::Dict{String, Dict{AcyclicElectricalTxChannel, Int}} # for every field ID contain a dict of removed LUTChannels together with the corresponding channel @@ -131,7 +138,7 @@ function ControlSequence(txCont::TxDAQController, target::Sequence) # Dict(PeriodicElectricalChannel => TxChannelParams) controlledChannelsDict = createControlledChannelsDict(seqControlledChannels, daq) # should this be changed to components instead of channels? - refIndices = createReferenceIndexMapping(controlledChannelsDict, daq) + refIndices, refTFs = createReferenceIndexMapping(controlledChannelsDict, daq) controlSequenceType = decideControlSequenceType(target, txCont.params.controlDC) @debug "ControlSequence: Decided on using ControlSequence of type $controlSequenceType" @@ -154,7 +161,7 @@ function ControlSequence(txCont::TxDAQController, target::Sequence) sinLUT, cosLUT = createLUTs(seqControlledChannels, currSeq) # TODO/JA: check if this makes a difference between target and currSeq, though it should not - return CrossCouplingControlSequence(target, currSeq, controlledChannelsDict, sinLUT, cosLUT, refIndices) + return CrossCouplingControlSequence(target, currSeq, controlledChannelsDict, refIndices, refTFs, sinLUT, cosLUT) elseif controlSequenceType == AWControlSequence # use the new controller @@ -165,7 +172,7 @@ function ControlSequence(txCont::TxDAQController, target::Sequence) rfftIndices = createRFFTindices(controlledChannelsDict, target, daq) - cont = AWControlSequence(target, currSeq, controlledChannelsDict, refIndices, rfftIndices, []) + cont = AWControlSequence(target, currSeq, controlledChannelsDict, refIndices, refTFs, rfftIndices, []) ## Apply last DC result if !isnothing(txCont.lastDCResults) && (txCont.lastChannelIDs == id.(seqControlledChannels)) @@ -214,7 +221,7 @@ function createRFFTindices(controlledChannelsDict::OrderedDict{PeriodicElectrica rfftSize = Int(div(rxNumSamplingPoints(seq),2)+1) index_mask = falses(numControlledChannels, numComponentsMax(daq)+1, rfftSize) - refChannelIdx = [channelIdx(daq, ch.feedback.channelID) for ch in collect(Base.values(controlledChannelsDict))] + refChannelIdx = [channelIdx(daq, ch.feedbackChannelID) for ch in collect(Base.values(controlledChannelsDict))] for (i, channel) in enumerate(keys(controlledChannelsDict)) @@ -282,9 +289,12 @@ function createReferenceIndexMapping(controlledChannelsDict::OrderedDict{Periodi # Dict(RedPitaya ChannelIndex => Index in daq.refChanIDs) mapping = Dict( b => a for (a,b) in enumerate(channelIdx(daq, daq.refChanIDs))) # RedPitaya ChannelIndex der Feedback-Kanäle in Reihenfolge der Kanäle im controlledChannelsDict - controlOrderChannelIndices = [channelIdx(daq, ch.feedback.channelID) for ch in collect(Base.values(controlledChannelsDict))] + controlOrderChannelIndices = [channelIdx(daq, ch.feedbackChannelID) for ch in collect(Base.values(controlledChannelsDict))] # Index in daq.refChanIDs in der Reihenfolge der Kanäle im controlledChannelsDict - return [mapping[x] for x in controlOrderChannelIndices] + refIndices = [mapping[x] for x in controlOrderChannelIndices] + # Feedback TransferFunction for the controlled Channels + refTFs = [feedbackTransferFunction(daq, ch) for ch in collect(Base.values(controlledChannelsDict))] + return refIndices, refTFs end function prepareSequenceForControl(txCont::TxDAQController, seq::Sequence) @@ -312,8 +322,8 @@ function createControlledChannelsDict(seqControlledChannels::Vector{PeriodicElec for seqChannel in seqControlledChannels name = id(seqChannel) daqChannel = get(daq.params.channels, name, nothing) - if isnothing(daqChannel) || isnothing(daqChannel.feedback) || !in(daqChannel.feedback.channelID, daq.refChanIDs) - @debug "Found missing control def: " name isnothing(daqChannel) isnothing(daqChannel.feedback) !in(daqChannel.feedback.channelID, daq.refChanIDs) + if isnothing(daqChannel) || isnothing(daqChannel.feedbackChannelID) || !in(daqChannel.feedbackChannelID, daq.refChanIDs) + @debug "Found missing control def: " name isnothing(daqChannel) isnothing(daqChannel.feedbackChannelID) !in(daqChannel.feedbackChannelID, daq.refChanIDs) push!(missingControlDef, name) else dict[seqChannel] = daqChannel @@ -641,7 +651,7 @@ function fieldAccuracyReached(cont::AWControlSequence, txCont::TxDAQController, end -updateControl!(cont::ControlSequence, txCont::TxDAQController, uRef) = updateControl!(cont, txCont, calcFieldFromRef(cont, uRef), calcDesiredField(cont)) +#updateControl!(cont::ControlSequence, txCont::TxDAQController, uRef) = updateControl!(cont, txCont, calcFieldFromRef(cont, uRef), calcDesiredField(cont)) function updateControl!(cont::ControlSequence, txCont::TxDAQController, Γ::Matrix{<:Complex}, Ω::Matrix{<:Complex}) @debug "Updating control values" κ = calcControlMatrix(cont) @@ -709,15 +719,6 @@ end ########## Functions for calculating the field matrix in T from the reference channels ################################################################################# - -# calcFieldFromRef(cont::CrossCouplingControlSequence, uRef; frame::Int64 = 1, period::Int64 = 1) = calcFieldFromRef(cont, uRef, UnsortedRef(), frame = frame, period = period) -# function calcFieldFromRef(cont::CrossCouplingControlSequence, uRef::Array{Float32, 4}, ::UnsortedRef; frame::Int64 = 1, period::Int64 = 1) -# return calcFieldFromRef(cont, uRef[:, :, :, frame], UnsortedRef(), period = period) -# end -# function calcFieldFromRef(cont::CrossCouplingControlSequence, uRef::Array{Float32, 3}, ::UnsortedRef; period::Int64 = 1) -# return calcFieldFromRef(cont, view(uRef[:, cont.refIndices, :], :, :, period), SortedRef()) -# end - function calcFieldsFromRef(cont::CrossCouplingControlSequence, uRef::Array{Float32, 4}) len = numControlledChannels(cont) N = rxNumSamplingPoints(cont.currSequence) @@ -728,11 +729,11 @@ function calcFieldsFromRef(cont::CrossCouplingControlSequence, uRef::Array{Float sorted = uRef[:, cont.refIndices, :, :] for i = 1:size(Γ, 4) for j = 1:size(Γ, 3) - calcFieldFromRef!(view(Γ, :, :, j, i), cont, view(sorted, :, :, j, i), SortedRef()) + _calcFieldFromRef!(view(Γ, :, :, j, i), cont, view(sorted, :, :, j, i), SortedRef()) end end for d =1:len - c = ustrip(u"T/V", getControlledDAQChannels(cont)[d].feedback.calibration(frequencies[d])) + c = ustrip(u"T/V", 1 ./cont.refTFs[d](frequencies[d])) for e=1:len correction = c * dividers[e]/dividers[d] * 2/N for j = 1:size(Γ, 3) @@ -752,28 +753,11 @@ function calcFieldsFromRef(cont::AWControlSequence, uRef::Array{Float32,4}) spectrum[1,:,:,:] ./= 2 sortedSpectrum = permutedims(spectrum[:, cont.refIndices, :, :], (2,1,3,4)) frequencies = ustrip.(u"Hz",rfftfreq(N, rxSamplingRate(cont.currSequence))) - fb_calibration = reduce(vcat, transpose([ustrip.(u"T/V", chan.feedback.calibration(frequencies)) for chan in getControlledDAQChannels(cont)])) - return sortedSpectrum.*fb_calibration -end - -function calcFieldFromRef(cont::CrossCouplingControlSequence, uRef, ::SortedRef) - len = numControlledChannels(cont) - N = rxNumSamplingPoints(cont.currSequence) - dividers = [divider.(getPrimaryComponents(cont))] - frequencies = ustrip(u"Hz", txBaseFrequency(cont.currSequence)) ./ dividers - Γ = zeros(ComplexF64, len, len) - calcFieldFromRef!(Γ, cont, uRef, SortedRef()) - for d =1:len - c = ustrip(u"T/V", getControlledDAQChannels(cont)[d].feedback.calibration(frequencies[d])) - for e=1:len - correction = c * dividers[e]/dividers[d] * 2/N - Γ[d,e] = correction * Γ[d,e] - end - end - return Γ + fbTF = reduce(vcat, transpose([ustrip.(u"V/T", tf(frequencies)) for tf in cont.refTFs])) + return sortedSpectrum./fbTF end -function calcFieldFromRef!(Γ::AbstractArray{ComplexF64, 2}, cont::CrossCouplingControlSequence, uRef, ::SortedRef) +function _calcFieldFromRef!(Γ::AbstractArray{ComplexF64, 2}, cont::CrossCouplingControlSequence, uRef, ::SortedRef) len = size(Γ, 1) for d=1:len for e=1:len diff --git a/src/Protocols/Storage/MDF.jl b/src/Protocols/Storage/MDF.jl index 13e6c776..15ec700a 100644 --- a/src/Protocols/Storage/MDF.jl +++ b/src/Protocols/Storage/MDF.jl @@ -310,7 +310,7 @@ function fillMDFAcquisition(mdf::MDFv2InMemory, scanner::MPIScanner, sequence::S MPIFiles.dfBaseFrequency(mdf, ustrip(u"Hz", dfBaseFrequency(sequence))) MPIFiles.dfCycle(mdf, ustrip(u"s", dfCycle(sequence))) dividers = dfDivider(sequence) - dividers[!isinteger.(dividers)] .= 0 + dividers[.!isinteger.(dividers)] .= 0 MPIFiles.dfDivider(mdf, Int.(dividers)) MPIFiles.dfNumChannels(mdf, dfNumChannels(sequence)) MPIFiles.dfPhase(mdf, ustrip.(u"rad", dfPhase(sequence))) @@ -327,13 +327,12 @@ function fillMDFAcquisition(mdf::MDFv2InMemory, scanner::MPIScanner, sequence::S MPIFiles.rxUnit(mdf, "V") # transferFunction - if hasTransferFunction(scanner) - numFreq = div(numSamplingPoints_,2)+1 - freq = collect(0:(numFreq-1))./(numFreq-1).*ustrip(u"Hz", rxBandwidth(sequence)) - tf_ = TransferFunction(scanner) - tf = ustrip.(tf_(freq,1:numRxChannels_)) # TODO/JA: check if sampleTF can be used here! - MPIFiles.rxTransferFunction(mdf, tf) - MPIFiles.rxInductionFactor(mdf, tf_.inductionFactor) + if any(hasTransferFunction.([getDAQ(scanner)], id.(rxChannels(sequence)))) + tfs = transferFunction.([getDAQ(scanner)], id.(rxChannels(sequence))) + tf = reduce((x,y)->MPIFiles.combine(x,y,interpolate=true), tfs) + sampledTF = ustrip.(sampleTF(tf, mdf)) + MPIFiles.rxTransferFunction(mdf, sampledTF) + MPIFiles.rxInductionFactor(mdf, tf.inductionFactor) end end diff --git a/src/Scanner.jl b/src/Scanner.jl index 3e3741e6..3c87a67f 100644 --- a/src/Scanner.jl +++ b/src/Scanner.jl @@ -183,8 +183,6 @@ Base.@kwdef struct MPIScannerGeneral defaultSequence::String = "" "Default protocol of the scanner." defaultProtocol::String = "" - "Location of the scanner's transfer function." - transferFunction::String = "" "Thread ID of the producer thread." producerThreadID::Int32 = 2 "Thread ID of the consumer thread." @@ -268,12 +266,6 @@ configDir(scanner::MPIScanner) = dirname(scanner.configFile) "General parameters of the scanner like its bore size or gradient." generalParams(scanner::MPIScanner) = scanner.generalParams -"Location of the scanner's transfer function." -transferFunction(scanner::MPIScanner) = scanner.generalParams.transferFunction - -"Check, whether the scanner has a transfer function defined." -hasTransferFunction(scanner::MPIScanner) = transferFunction(scanner) != "" - """ $(SIGNATURES) @@ -466,8 +458,6 @@ function MPIFiles.TransferFunction(configdir::AbstractString, name::AbstractStri return TransferFunction(path) end -MPIFiles.TransferFunction(scanner::MPIScanner) = TransferFunction(configDir(scanner),transferFunction(scanner)) - #### Protocol #### function getProtocolList(scanner::MPIScanner) path = joinpath(configDir(scanner), "Protocols/") diff --git a/src/Sequences/PeriodicElectricalChannel.jl b/src/Sequences/PeriodicElectricalChannel.jl index 3425028f..0b8e5f37 100644 --- a/src/Sequences/PeriodicElectricalChannel.jl +++ b/src/Sequences/PeriodicElectricalChannel.jl @@ -169,7 +169,7 @@ function createChannelComponent(componentID::AbstractString, ::Type{ArbitraryEle if componentDict["values"] isa AbstractString # case 1: filename to waveform filename = joinpath(homedir(), ".mpi", "Waveforms", componentDict["values"]) try - values = h5read(filename, "/values") + values = reshape(h5read(filename, "/values"),:) catch throw(SequenceConfigurationError("Could not load the waveform $(componentDict["values"]), either the file does not exist at $filename or the file structure is wrong")) end diff --git a/src/Utils/DictToStruct.jl b/src/Utils/DictToStruct.jl index cad537f1..8618699d 100644 --- a/src/Utils/DictToStruct.jl +++ b/src/Utils/DictToStruct.jl @@ -46,6 +46,16 @@ function parsePossibleURange(val::String) end parsePossibleURange(val::Vector{String}) = parsePossibleURange.(val) +function parse_into_tf(value::String) + if occursin(".h5", value) # case 1: filename to transfer function, the TF will be read the first time calibration() is called, (done in _init(), to prevent delays while using the device) + calibration_tf = value + else # case 2: single value, extended into transfer function with no frequency dependency + calibration_value = uparse(value) + calibration_tf = TransferFunction([0,10e6],ComplexF64[ustrip(calibration_value), ustrip(calibration_value)], units=[unit(calibration_value)]) + end + return calibration_tf +end + function dict_to_splatting(dict::Dict) splattingDict = Dict{Symbol, Any}() for (key, value) in dict From e4b7a47757fc3593c84bef5b8adb1cd2fa04b973 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Thu, 19 Sep 2024 15:10:49 +0200 Subject: [PATCH 120/168] added missing function for LUT channels --- src/Devices/DAQ/DAQ.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Devices/DAQ/DAQ.jl b/src/Devices/DAQ/DAQ.jl index 2fe53a34..abcb8c69 100644 --- a/src/Devices/DAQ/DAQ.jl +++ b/src/Devices/DAQ/DAQ.jl @@ -275,6 +275,7 @@ function transferFunction(dev::Union{MPIScanner, AbstractDAQ}, channel::DAQRxCha end calibration(daq::AbstractDAQ, channelID::AbstractString) = calibration(daq, channel(daq,channelID)) +calibration(dev::Device, channel::RedPitayaLUTChannelParams) = channel.calibration function calibration(dev::Union{MPIScanner, AbstractDAQ}, channel::DAQTxChannelParams) if isa(channel.calibration, String) channel.calibration = TransferFunction(joinpath(configDir(dev), "TransferFunctions", channel.calibration)) From e65256f89cdd7f3d86053b309c9df16aee5c2fed Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Mon, 7 Oct 2024 09:26:42 +0200 Subject: [PATCH 121/168] small fixes --- src/Devices/DAQ/DAQ.jl | 1 - src/Devices/DAQ/RedPitayaDAQ.jl | 2 ++ src/Protocols/MPSMeasurementProtocol.jl | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Devices/DAQ/DAQ.jl b/src/Devices/DAQ/DAQ.jl index abcb8c69..2fe53a34 100644 --- a/src/Devices/DAQ/DAQ.jl +++ b/src/Devices/DAQ/DAQ.jl @@ -275,7 +275,6 @@ function transferFunction(dev::Union{MPIScanner, AbstractDAQ}, channel::DAQRxCha end calibration(daq::AbstractDAQ, channelID::AbstractString) = calibration(daq, channel(daq,channelID)) -calibration(dev::Device, channel::RedPitayaLUTChannelParams) = channel.calibration function calibration(dev::Union{MPIScanner, AbstractDAQ}, channel::DAQTxChannelParams) if isa(channel.calibration, String) channel.calibration = TransferFunction(joinpath(configDir(dev), "TransferFunctions", channel.calibration)) diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index d2594914..655000a6 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -30,6 +30,8 @@ Base.@kwdef struct RedPitayaLUTChannelParams <: TxChannelParams switchEnable::Bool = true end +calibration(dev::Device, channel::RedPitayaLUTChannelParams) = channel.calibration + function createDAQChannel(::Type{RedPitayaLUTChannelParams}, dict::Dict{String, Any}) splattingDict = Dict{Symbol, Any}() splattingDict[:channelIdx] = dict["channel"] diff --git a/src/Protocols/MPSMeasurementProtocol.jl b/src/Protocols/MPSMeasurementProtocol.jl index 0112b235..f1b34b32 100644 --- a/src/Protocols/MPSMeasurementProtocol.jl +++ b/src/Protocols/MPSMeasurementProtocol.jl @@ -293,8 +293,8 @@ function SequenceMeasState(protocol::MPSMeasurementProtocol) buffers = StorageBuffer[buffer] if protocol.params.controlTx - len = length(keys(sequence.simpleChannel)) - push!(buffers, DriveFieldBuffer(1, zeros(ComplexF64, len, len, 1, numFrames), sequence)) + #len = length(keys(sequence.refIndices)) + push!(buffers, DriveFieldBuffer(1, zeros(ComplexF64, controlMatrixShape(sequence)..., 1, numFrames), sequence)) end buffer = FrameSplitterBuffer(daq, StorageBuffer[buffer]) From 197347be584e3c95b678493964243958f8030b31 Mon Sep 17 00:00:00 2001 From: "mpiuser (IMT MPS)" <> Date: Mon, 7 Oct 2024 10:29:55 +0200 Subject: [PATCH 122/168] fixes for Hybrid SM Protocol --- src/Devices/DAQ/RedPitayaDAQ.jl | 2 +- src/Protocols/MPSMeasurementProtocol.jl | 44 +++++++++---------- .../MultiSequenceSystemMatrixProtocol.jl | 4 +- src/Protocols/Storage/ChainableBuffer.jl | 5 ++- src/Protocols/Storage/MDF.jl | 2 +- 5 files changed, 28 insertions(+), 29 deletions(-) diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index 35a31cba..5313508c 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -777,7 +777,7 @@ function prepareOffsetSwitches(offsets::Vector{Vector{T}}, channels::Vector{Prot resultAnySwitching = Bool[] # Store when any channel is switching previous = CartesianIndex(Tuple(fill(-1, length(iterations)))) # This will be used to detect which index is currently "running"/changing - for indices in eachindex(loop) + for indices in loop value = offsetVec[indices[channelIdx]] # Apply (loop) switch steps for each changing index, i.e. an index that is not equal to its previous iteration value diff --git a/src/Protocols/MPSMeasurementProtocol.jl b/src/Protocols/MPSMeasurementProtocol.jl index 0112b235..d2b303bb 100644 --- a/src/Protocols/MPSMeasurementProtocol.jl +++ b/src/Protocols/MPSMeasurementProtocol.jl @@ -22,7 +22,7 @@ Base.@kwdef mutable struct MPSMeasurementProtocolParams <: ProtocolParams "Sort patches" sortPatches::Bool = true "Flag if the measurement should be saved as a system matrix or not" - saveAsSystemMatrix::Bool = true + saveInCalibFolder::Bool = true "Number of periods per offset of the MPS offset measurement. Overwrites parts of the sequence definition." dfPeriodsPerOffset::Integer = 2 @@ -96,7 +96,7 @@ function _init(protocol::MPSMeasurementProtocol) protocol.bgMeas = zeros(Float32,0,0,0,0) protocol.protocolMeasState = ProtocolMeasState() - try + #try seq, perm, offsets, calibsize, numPeriodsPerFrame = prepareProtocolSequences(protocol.params.sequence, getDAQ(scanner(protocol)); numPeriodsPerOffset = protocol.params.dfPeriodsPerOffset) # For each patch assign nothing if invalid or otherwise index in "proper" frame @@ -119,9 +119,9 @@ function _init(protocol::MPSMeasurementProtocol) protocol.offsetfields = ustrip.(u"T", offsets) # TODO make robust protocol.calibsize = calibsize @debug "Prepared Protocol Sequence: $(length(patchPerm)) measured and $(length(perm)) valid patches in Permutation" - catch e - throw(e) - end + #catch e + # throw(e) + #end return nothing end @@ -290,14 +290,14 @@ function SequenceMeasState(protocol::MPSMeasurementProtocol) bufferSize = (rxNumSamplingPoints(protocol.sequence), length(rxChannels(protocol.sequence)), 1, numFrames) buffer = FrameBuffer(protocol, "meas.bin", Float32, bufferSize) - buffers = StorageBuffer[buffer] + # buffers = StorageBuffer[buffer] - if protocol.params.controlTx - len = length(keys(sequence.simpleChannel)) - push!(buffers, DriveFieldBuffer(1, zeros(ComplexF64, len, len, 1, numFrames), sequence)) - end + # if protocol.params.controlTx + # len = length(keys(sequence.simpleChannel)) + # push!(buffers, DriveFieldBuffer(1, zeros(ComplexF64, len, len, 1, numFrames), sequence)) + # end - buffer = FrameSplitterBuffer(daq, StorageBuffer[buffer]) + # buffer = FrameSplitterBuffer(daq, StorageBuffer[buffer]) buffer = MPSBuffer(buffer, protocol.patchPermutation, numFrames, 1, acqNumPeriodsPerFrame(protocol.sequence)) channel = Channel{channelType(daq)}(32) @@ -414,7 +414,7 @@ function handleEvent(protocol::MPSMeasurementProtocol, event::DatasetStoreStorag offsetPerm = zeros(Int64, size(data, 3)) for (index, patch) in enumerate(protocol.patchPermutation) if !isnothing(patch) - offsetPerm[patch] = index÷periodsPerOffset + offsetPerm[patch] = (index-1)÷periodsPerOffset + 1 end end offsets = protocol.offsetfields[offsetPerm, :] @@ -427,17 +427,15 @@ function handleEvent(protocol::MPSMeasurementProtocol, event::DatasetStoreStorag filename = nothing - if protocol.params.saveAsSystemMatrix - periodsPerOffset = protocol.params.averagePeriodsPerOffset ? 1 : protocol.params.dfPeriodsPerOffset - isBGFrame = repeat(isBGFrame, inner = div(size(data, 3), periodsPerOffset)) - data = reshape(data, size(data, 1), size(data, 2), periodsPerOffset, :) - # All periods in one frame (should) have same offset - offsets = reshape(offsets, periodsPerOffset, :, size(offsets, 2))[1, :, :] - offsets = reshape(offsets, protocol.calibsize..., :) # make calib size "visible" to storing function - filename = saveasMDF(store, scanner, sequence, data, offsets, isBGFrame, mdf, storeAsSystemMatrix=protocol.params.saveAsSystemMatrix, drivefield = drivefield, temperatures = temperature, applied = appliedField) - else - filename = saveasMDF(store, scanner, sequence, data, isBGFrame, mdf, drivefield = drivefield, temperatures = temperature, applied = appliedField) - end + + periodsPerOffset = protocol.params.averagePeriodsPerOffset ? 1 : protocol.params.dfPeriodsPerOffset + isBGFrame = repeat(isBGFrame, inner = div(size(data, 3), periodsPerOffset)) + data = reshape(data, size(data, 1), size(data, 2), periodsPerOffset, :) + # All periods in one frame (should) have same offset + offsets = reshape(offsets, periodsPerOffset, :, size(offsets, 2))[1, :, :] + offsets = reshape(offsets, protocol.calibsize..., :) # make calib size "visible" to storing function + filename = saveasMDF(store, scanner, sequence, data, offsets, isBGFrame, mdf, storeAsSystemMatrix=protocol.params.saveInCalibFolder, drivefield = drivefield, temperatures = temperature, applied = appliedField) + @info "The measurement was saved at `$filename`." put!(protocol.biChannel, StorageSuccessEvent(filename)) end diff --git a/src/Protocols/MultiSequenceSystemMatrixProtocol.jl b/src/Protocols/MultiSequenceSystemMatrixProtocol.jl index 8a4efa02..0d8093a3 100644 --- a/src/Protocols/MultiSequenceSystemMatrixProtocol.jl +++ b/src/Protocols/MultiSequenceSystemMatrixProtocol.jl @@ -12,7 +12,7 @@ Base.@kwdef mutable struct MultiSequenceSystemMatrixProtocolParams <: ProtocolPa "SM Positions mapped to the natural sorting of sequence tomls" positions::Union{Positions, Nothing} = nothing "Flag if the calibration should be saved as a system matrix or not" - saveAsSystemMatrix::Bool = true + saveInCalibFolder::Bool = true "Seconds to wait between measurements" waitTime::Float64 = 0.0 end @@ -486,7 +486,7 @@ function handleEvent(protocol::MultiSequenceSystemMatrixProtocol, event::Dataset if !isempty(protocol.systemMeasState.applied) applied = protocol.systemMeasState.applied end - filename = saveasMDF(store, scanner, protocol.params.sequences[1], data, positions, isBackgroundFrame, mdf; storeAsSystemMatrix=protocol.params.saveAsSystemMatrix, temperatures=temperatures, drivefield=drivefield, applied=applied) + filename = saveasMDF(store, scanner, protocol.params.sequences[1], data, positions, isBackgroundFrame, mdf; storeAsSystemMatrix=protocol.params.saveInCalibFolder, temperatures=temperatures, drivefield=drivefield, applied=applied) @show filename put!(protocol.biChannel, StorageSuccessEvent(filename)) end diff --git a/src/Protocols/Storage/ChainableBuffer.jl b/src/Protocols/Storage/ChainableBuffer.jl index 6be5790c..6b7ae1dc 100644 --- a/src/Protocols/Storage/ChainableBuffer.jl +++ b/src/Protocols/Storage/ChainableBuffer.jl @@ -69,9 +69,10 @@ function FrameBuffer(protocol::Protocol, file::String, sequence::Sequence) numChannel = length(rxChannels(sequence)) return FrameBuffer(protocol, file, Float32, (rxNumSamplingPoints, numChannel, numPeriods, numFrames)) end -function FrameBuffer(protocol::Protocol, file::String, args...) +function FrameBuffer(protocol::Protocol, f::String, args...) @debug "Creating memory-mapped FrameBuffer with size $(args[2])" - mapped = mmap!(protocol, file, args...) + rm(file(protocol, f), force=true) + mapped = mmap!(protocol, f, args...) return FrameBuffer(1, mapped) end diff --git a/src/Protocols/Storage/MDF.jl b/src/Protocols/Storage/MDF.jl index 13e6c776..f504f664 100644 --- a/src/Protocols/Storage/MDF.jl +++ b/src/Protocols/Storage/MDF.jl @@ -310,7 +310,7 @@ function fillMDFAcquisition(mdf::MDFv2InMemory, scanner::MPIScanner, sequence::S MPIFiles.dfBaseFrequency(mdf, ustrip(u"Hz", dfBaseFrequency(sequence))) MPIFiles.dfCycle(mdf, ustrip(u"s", dfCycle(sequence))) dividers = dfDivider(sequence) - dividers[!isinteger.(dividers)] .= 0 + dividers[.!isinteger.(dividers)] .= 0 MPIFiles.dfDivider(mdf, Int.(dividers)) MPIFiles.dfNumChannels(mdf, dfNumChannels(sequence)) MPIFiles.dfPhase(mdf, ustrip.(u"rad", dfPhase(sequence))) From f55801f36b6952917de5e0ab9bd9ba5a7919b7cf Mon Sep 17 00:00:00 2001 From: "mpiuser (IMT MPS)" <> Date: Mon, 7 Oct 2024 10:33:38 +0200 Subject: [PATCH 123/168] save in study folder by default --- src/Protocols/MPSMeasurementProtocol.jl | 2 +- src/Protocols/MultiSequenceSystemMatrixProtocol.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Protocols/MPSMeasurementProtocol.jl b/src/Protocols/MPSMeasurementProtocol.jl index d2b303bb..24a14c52 100644 --- a/src/Protocols/MPSMeasurementProtocol.jl +++ b/src/Protocols/MPSMeasurementProtocol.jl @@ -22,7 +22,7 @@ Base.@kwdef mutable struct MPSMeasurementProtocolParams <: ProtocolParams "Sort patches" sortPatches::Bool = true "Flag if the measurement should be saved as a system matrix or not" - saveInCalibFolder::Bool = true + saveInCalibFolder::Bool = false "Number of periods per offset of the MPS offset measurement. Overwrites parts of the sequence definition." dfPeriodsPerOffset::Integer = 2 diff --git a/src/Protocols/MultiSequenceSystemMatrixProtocol.jl b/src/Protocols/MultiSequenceSystemMatrixProtocol.jl index 0d8093a3..0da94494 100644 --- a/src/Protocols/MultiSequenceSystemMatrixProtocol.jl +++ b/src/Protocols/MultiSequenceSystemMatrixProtocol.jl @@ -12,7 +12,7 @@ Base.@kwdef mutable struct MultiSequenceSystemMatrixProtocolParams <: ProtocolPa "SM Positions mapped to the natural sorting of sequence tomls" positions::Union{Positions, Nothing} = nothing "Flag if the calibration should be saved as a system matrix or not" - saveInCalibFolder::Bool = true + saveInCalibFolder::Bool = false "Seconds to wait between measurements" waitTime::Float64 = 0.0 end From 5ac273fdb4482744b42fb38419809f6d9c671a39 Mon Sep 17 00:00:00 2001 From: jonschumacher Date: Fri, 11 Oct 2024 11:52:36 +0200 Subject: [PATCH 124/168] small fix --- src/Devices/Virtual/TxDAQController.jl | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index 621a49c8..718f2313 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -540,6 +540,12 @@ end setup(daq::AbstractDAQ, sequence::ControlSequence) = setup(daq, getControlResult(sequence)) +function updateCachedCalibration(txCont::TxDAQController, cont::ControlSequence) + finalCalibration = calcControlMatrix(cont) ./ calcDesiredField(cont) + calibrationResults = finalCalibration[.!isnan.(finalCalibration)] + @debug "Control result: You could update the forward calibration in the Scanner.toml to this value for faster control" calibrationResults + nothing +end function updateCachedCalibration(txCont::TxDAQController, cont::AWControlSequence) finalCalibration = calcControlMatrix(cont) ./ calcDesiredField(cont) @@ -606,8 +612,7 @@ end fieldAccuracyReached(cont::ControlSequence, txCont::TxDAQController, Γ::Matrix{<:Complex}) = fieldAccuracyReached(cont, txCont, Γ, calcDesiredField(cont)) function fieldAccuracyReached(cont::CrossCouplingControlSequence, txCont::TxDAQController, Γ::Matrix{<:Complex}, Ω::Matrix{<:Complex}) - diff = Ω - Γ - abs_deviation = abs.(diff) + abs_deviation = abs.(Ω) .- abs.(Γ) rel_deviation = abs_deviation ./ abs.(Ω) rel_deviation[abs.(Ω).<1e-15] .= 0 # relative deviation does not make sense for a zero goal phase_deviation = angle.(Ω).-angle.(Γ) From 09ae3728c6d32ee42aba21812bde17c3cb32b4fd Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Wed, 16 Oct 2024 19:01:54 +0200 Subject: [PATCH 125/168] reset cache on control error --- src/Devices/Virtual/TxDAQController.jl | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index 621a49c8..a2872af3 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -459,6 +459,7 @@ function controlTx(txCont::TxDAQController, control::ControlSequence) end catch ex @error "Exception during control loop" exception=(ex, catch_backtrace()) + resetCachedCalibration(txCont) controlPhaseError = ex finally try @@ -560,7 +561,15 @@ function updateCachedCalibration(txCont::TxDAQController, cont::AWControlSequenc txCont.lastDCResults = cont.dcSearch[end-1:end] txCont.lastChannelIDs = channelIDs - @info "Cached DC result" txCont.lastDCResults + @debug "Cached DC result" txCont.lastDCResults +end + +function resetCachedCalibration(txCont::TxDAQController) + txCont.controlResults = Dict{String, Union{typeof(1.0im*u"V/T"), Dict{Float64,typeof(1.0im*u"V/T")}}}() + txCont.lastDCResults = nothing + txCont.lastChannelIDs = String[] + @debug "Reset cached calibration" + nothing end From 7ac0f51a195a9134535a57e1f659ad76ac2b55b1 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Wed, 16 Oct 2024 19:03:27 +0200 Subject: [PATCH 126/168] cleaned up protocol events (stop->pause) --- src/Protocols/ContinousMeasurementProtocol.jl | 8 +--- src/Protocols/MPIForceProtocol.jl | 11 +----- src/Protocols/MPIMeasurementProtocol.jl | 13 ------- src/Protocols/MPSMeasurementProtocol.jl | 13 ------- .../MechanicalMPIMeasurementProtocol.jl | 12 ------ src/Protocols/Protocol.jl | 37 +++++++++++++++---- .../RobotBasedMagneticFieldStaticProtocol.jl | 4 +- src/Protocols/RobotBasedProtocol.jl | 2 +- .../RobotBasedSystemMatrixProtocol.jl | 4 +- .../RobotBasedTDesignFieldProtocol.jl | 7 +--- src/Protocols/RobotMPIMeasurementProtocol.jl | 13 ------- 11 files changed, 39 insertions(+), 85 deletions(-) diff --git a/src/Protocols/ContinousMeasurementProtocol.jl b/src/Protocols/ContinousMeasurementProtocol.jl index 38cac750..b63b4bf2 100644 --- a/src/Protocols/ContinousMeasurementProtocol.jl +++ b/src/Protocols/ContinousMeasurementProtocol.jl @@ -122,7 +122,7 @@ function _execute(protocol::ContinousMeasurementProtocol) while !measPauseOver || protocol.stopped handleEvents(protocol) if !notifiedStop && protocol.stopped - put!(protocol.biChannel, OperationSuccessfulEvent(StopEvent())) + put!(protocol.biChannel, OperationSuccessfulEvent(PauseEvent())) notifiedStop = true end if notifiedStop && !protocol.stopped @@ -198,11 +198,7 @@ function asyncMeasurement(protocol::ContinousMeasurementProtocol) return protocol.seqMeasState end -function cleanup(protocol::ContinousMeasurementProtocol) - # NOP -end - -function stop(protocol::ContinousMeasurementProtocol) +function pause(protocol::ContinousMeasurementProtocol) protocol.stopped = true end diff --git a/src/Protocols/MPIForceProtocol.jl b/src/Protocols/MPIForceProtocol.jl index 4aa0ad94..b32470bc 100644 --- a/src/Protocols/MPIForceProtocol.jl +++ b/src/Protocols/MPIForceProtocol.jl @@ -166,19 +166,10 @@ function performExperiment(protocol::MPIForceProtocol) end end - -function cleanup(protocol::MPIForceProtocol) - # NOP -end - -function stop(protocol::MPIForceProtocol) +function pause(protocol::MPIForceProtocol) protocol.stopped = true end -function resume(protocol::MPIForceProtocol) - put!(protocol.biChannel, OperationNotSupportedEvent(ResumeEvent())) -end - function cancel(protocol::MPIForceProtocol) protocol.cancelled = true end diff --git a/src/Protocols/MPIMeasurementProtocol.jl b/src/Protocols/MPIMeasurementProtocol.jl index cad1647c..44396b5e 100644 --- a/src/Protocols/MPIMeasurementProtocol.jl +++ b/src/Protocols/MPIMeasurementProtocol.jl @@ -205,19 +205,6 @@ function asyncMeasurement(protocol::MPIMeasurementProtocol) return protocol.seqMeasState end - -function cleanup(protocol::MPIMeasurementProtocol) - # NOP -end - -function stop(protocol::MPIMeasurementProtocol) - put!(protocol.biChannel, OperationNotSupportedEvent(StopEvent())) -end - -function resume(protocol::MPIMeasurementProtocol) - put!(protocol.biChannel, OperationNotSupportedEvent(ResumeEvent())) -end - function cancel(protocol::MPIMeasurementProtocol) protocol.cancelled = true #put!(protocol.biChannel, OperationNotSupportedEvent(CancelEvent())) diff --git a/src/Protocols/MPSMeasurementProtocol.jl b/src/Protocols/MPSMeasurementProtocol.jl index f1b34b32..6c1170be 100644 --- a/src/Protocols/MPSMeasurementProtocol.jl +++ b/src/Protocols/MPSMeasurementProtocol.jl @@ -340,19 +340,6 @@ function push!(mpsBuffer::MPSBuffer, frames::Array{T,4}) where T end sinks!(buffer::MPSBuffer, sinks::Vector{SinkBuffer}) = sinks!(buffer.target, sinks) - -function cleanup(protocol::MPSMeasurementProtocol) - # NOP -end - -function stop(protocol::MPSMeasurementProtocol) - put!(protocol.biChannel, OperationNotSupportedEvent(StopEvent())) -end - -function resume(protocol::MPSMeasurementProtocol) - put!(protocol.biChannel, OperationNotSupportedEvent(ResumeEvent())) -end - function cancel(protocol::MPSMeasurementProtocol) protocol.cancelled = true #put!(protocol.biChannel, OperationNotSupportedEvent(CancelEvent())) diff --git a/src/Protocols/MechanicalMPIMeasurementProtocol.jl b/src/Protocols/MechanicalMPIMeasurementProtocol.jl index b9015edc..9cab5269 100644 --- a/src/Protocols/MechanicalMPIMeasurementProtocol.jl +++ b/src/Protocols/MechanicalMPIMeasurementProtocol.jl @@ -215,18 +215,6 @@ function asyncMeasurement(protocol::MechanicalMPIMeasurementProtocol) return protocol.seqMeasState end -function cleanup(protocol::MechanicalMPIMeasurementProtocol) - # NOP -end - -function stop(protocol::MechanicalMPIMeasurementProtocol) - put!(protocol.biChannel, OperationNotSupportedEvent(StopEvent())) -end - -function resume(protocol::MechanicalMPIMeasurementProtocol) - put!(protocol.biChannel, OperationNotSupportedEvent(ResumeEvent())) -end - function cancel(protocol::MechanicalMPIMeasurementProtocol) protocol.cancelled = true #put!(protocol.biChannel, OperationNotSupportedEvent(CancelEvent())) diff --git a/src/Protocols/Protocol.jl b/src/Protocols/Protocol.jl index 9fe02129..8f774eb6 100644 --- a/src/Protocols/Protocol.jl +++ b/src/Protocols/Protocol.jl @@ -7,8 +7,8 @@ export Protocol, ProtocolParams, name, description, scanner, params, abstract type ProtocolParams end -export ProtocolState, PS_UNDEFINED, PS_INIT, PS_RUNNING, PS_PAUSED, PS_FINISHED, PS_FAILED -@enum ProtocolState PS_UNDEFINED PS_INIT PS_RUNNING PS_PAUSED PS_FINISHED PS_FAILED +export ProtocolState, PS_UNDEFINED, PS_INIT, PS_RUNNING, PS_PAUSED, PS_FINISHED, PS_FAILED, PS_STOPPED +@enum ProtocolState PS_UNDEFINED PS_INIT PS_RUNNING PS_PAUSED PS_FINISHED PS_FAILED PS_STOPPED export @add_protocol_fields macro add_protocol_fields(paramType) @@ -95,10 +95,29 @@ abstract type ProtocolEvent end @mustimplement _init(protocol::Protocol) # Prepare necessary data structures, ex. buffer for samples + background meas @mustimplement _execute(protocol::Protocol) @mustimplement enterExecute(protocol::Protocol) # Prepare execution tracking flags, ex. execution done or cancelled flags -@mustimplement cleanup(protocol::Protocol) -@mustimplement stop(protocol::Protocol) -@mustimplement resume(protocol::Protocol) -@mustimplement cancel(protocol::Protocol) + + +### all Protocols must implement these events +function cleanup(protocol::Protocol) + # NOP +end + +function pause(protocol::Protocol) + put!(protocol.biChannel, OperationNotSupportedEvent(PauseEvent())) +end + +function resume(protocol::Protocol) + put!(protocol.biChannel, OperationNotSupportedEvent(ResumeEvent())) +end + +function stop(protocol::Protocol) + put!(protocol.biChannel, OperationNotSupportedEvent(StopEvent())) +end + +function cancel(protocol::Protocol) + put!(protocol.biChannel, OperationNotSupportedEvent(CancelEvent())) +end +### function init(protocol::Protocol) @debug "Initializing protocol $(name(protocol)) with inner initializer." @@ -276,13 +295,15 @@ function askChoices(protocol::Protocol, message::AbstractString, choices::Vector end end -handleEvent(protocol::Protocol, event::PauseEvent) = stop(protocol) -handleEvent(protocol::Protocol, event::StopEvent) = stop(protocol) # TODO Differentiate stop and pause +handleEvent(protocol::Protocol, event::PauseEvent) = pause(protocol) +handleEvent(protocol::Protocol, event::StopEvent) = stop(protocol) handleEvent(protocol::Protocol, event::ResumeEvent) = resume(protocol) handleEvent(protocol::Protocol, event::CancelEvent) = cancel(protocol) handleEvent(protocol::Protocol, event::ProtocolEvent) = put!(biChannel(protocol), UndefinedEvent(event)) #handleEvent(protocol::Protocol, event::InfoQueryEvent) = +# by default all event are not supported + function handleEvents(protocol::Protocol) while isready(protocol.biChannel) event = take!(protocol.biChannel) diff --git a/src/Protocols/RobotBasedMagneticFieldStaticProtocol.jl b/src/Protocols/RobotBasedMagneticFieldStaticProtocol.jl index 091a28ba..b7617b45 100644 --- a/src/Protocols/RobotBasedMagneticFieldStaticProtocol.jl +++ b/src/Protocols/RobotBasedMagneticFieldStaticProtocol.jl @@ -169,13 +169,13 @@ function performMeasurement(protocol::RobotBasedMagneticFieldStaticProtocol) addMeasuredPosition(protocol.measurement, nextPosition(protocol).data, field=field_) # fieldError=fieldError_, fieldFrequency=fieldFrequency_, timestamp=timestamp_, temperature=temperature_) end -function stop(protocol::RobotBasedMagneticFieldStaticProtocol) +function pause(protocol::RobotBasedMagneticFieldStaticProtocol) if protocol.currPos <= length(protocol.params.positions) # OperationSuccessfulEvent is put when it actually is in the stop loop protocol.stopped = true else # Stopped has no concept once all measurements are done - put!(protocol.biChannel, OperationUnsuccessfulEvent(StopEvent())) + put!(protocol.biChannel, OperationUnsuccessfulEvent(PauseEvent())) end end diff --git a/src/Protocols/RobotBasedProtocol.jl b/src/Protocols/RobotBasedProtocol.jl index a04aeb38..b2e8e4df 100644 --- a/src/Protocols/RobotBasedProtocol.jl +++ b/src/Protocols/RobotBasedProtocol.jl @@ -31,7 +31,7 @@ function _execute(protocol::RobotBasedProtocol) handleEvents(protocol) protocol.cancelled && throw(CancelException()) if !notifiedStop - put!(protocol.biChannel, OperationSuccessfulEvent(StopEvent())) + put!(protocol.biChannel, OperationSuccessfulEvent(PauseEvent())) notifiedStop = true end if !protocol.stopped diff --git a/src/Protocols/RobotBasedSystemMatrixProtocol.jl b/src/Protocols/RobotBasedSystemMatrixProtocol.jl index 68d600f9..ce255a7d 100644 --- a/src/Protocols/RobotBasedSystemMatrixProtocol.jl +++ b/src/Protocols/RobotBasedSystemMatrixProtocol.jl @@ -473,14 +473,14 @@ function restore(protocol::RobotBasedSystemMatrixProtocol) end -function stop(protocol::RobotBasedSystemMatrixProtocol) +function pause(protocol::RobotBasedSystemMatrixProtocol) calib = protocol.systemMeasState if calib.currPos <= length(calib.positions) # OperationSuccessfulEvent is put when it actually is in the stop loop protocol.stopped = true else # Stopped has no concept once all measurements are done - put!(protocol.biChannel, OperationUnsuccessfulEvent(StopEvent())) + put!(protocol.biChannel, OperationUnsuccessfulEvent(PauseEvent())) end end diff --git a/src/Protocols/RobotBasedTDesignFieldProtocol.jl b/src/Protocols/RobotBasedTDesignFieldProtocol.jl index f94a928c..ef4da9dd 100644 --- a/src/Protocols/RobotBasedTDesignFieldProtocol.jl +++ b/src/Protocols/RobotBasedTDesignFieldProtocol.jl @@ -211,13 +211,13 @@ function finishMeasurement(protocol::RobotBasedTDesignFieldProtocol, gauss::Lake end end -function stop(protocol::RobotBasedTDesignFieldProtocol) +function pause(protocol::RobotBasedTDesignFieldProtocol) if protocol.currPos <= length(protocol.positions) # OperationSuccessfulEvent is put when it actually is in the stop loop protocol.stopped = true else # Stopped has no concept once all measurements are done - put!(protocol.biChannel, OperationUnsuccessfulEvent(StopEvent())) + put!(protocol.biChannel, OperationUnsuccessfulEvent(PauseEvent())) end end @@ -252,8 +252,5 @@ function handleEvent(protocol::RobotBasedTDesignFieldProtocol, event::FileStorag put!(protocol.biChannel, StorageSuccessEvent(filename)) end -function cleanup(protocol::RobotBasedTDesignFieldProtocol) - # NOP -end protocolInteractivity(protocol::RobotBasedTDesignFieldProtocol) = Interactive() diff --git a/src/Protocols/RobotMPIMeasurementProtocol.jl b/src/Protocols/RobotMPIMeasurementProtocol.jl index 9d082336..50ce1b8f 100644 --- a/src/Protocols/RobotMPIMeasurementProtocol.jl +++ b/src/Protocols/RobotMPIMeasurementProtocol.jl @@ -236,19 +236,6 @@ function asyncMeasurement(protocol::RobotMPIMeasurementProtocol) return protocol.seqMeasState end - -function cleanup(protocol::RobotMPIMeasurementProtocol) - # NOP -end - -function stop(protocol::RobotMPIMeasurementProtocol) - put!(protocol.biChannel, OperationNotSupportedEvent(StopEvent())) -end - -function resume(protocol::RobotMPIMeasurementProtocol) - put!(protocol.biChannel, OperationNotSupportedEvent(ResumeEvent())) -end - function cancel(protocol::RobotMPIMeasurementProtocol) protocol.cancelled = true #put!(protocol.biChannel, OperationNotSupportedEvent(CancelEvent())) From ce44dd226341943a765f888422ca5a5ef23f929e Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Wed, 16 Oct 2024 19:14:22 +0200 Subject: [PATCH 127/168] QOL improvements for MultiSequenceProtocol --- src/Protocols/MPSMeasurementProtocol.jl | 16 ----- .../MultiSequenceSystemMatrixProtocol.jl | 70 ++++++++++++------- src/Utils/Utils.jl | 20 +++++- 3 files changed, 62 insertions(+), 44 deletions(-) diff --git a/src/Protocols/MPSMeasurementProtocol.jl b/src/Protocols/MPSMeasurementProtocol.jl index 6c1170be..48f1c507 100644 --- a/src/Protocols/MPSMeasurementProtocol.jl +++ b/src/Protocols/MPSMeasurementProtocol.jl @@ -136,22 +136,6 @@ function timeEstimate(protocol::MPSMeasurementProtocol) txSamplesPerFrame = lcm(dfDivider(seq)) * size(protocol.patchPermutation, 1) fgTime = (txSamplesPerFrame * fgFrames) / txBaseFrequency(seq) |> u"s" bgTime = (txSamplesPerFrame * bgFrames) / txBaseFrequency(seq) |> u"s" - function timeFormat(t) - v = ustrip(u"s",t) - if v>3600 - x = Int((v%3600)÷60) - return "$(Int(v÷3600)):$(if x<10; " " else "" end)$(x) h" - elseif v>60 - x = round(v%60,digits=1) - return "$(Int(v÷60)):$(if x<10; " " else "" end)$(x) min" - elseif v>0.5 - return "$(round(v,digits=2)) s" - elseif v>0.5e-3 - return "$(round(v*1e3,digits=2)) ms" - else - return "$(round(v*1e6,digits=2)) µs" - end - end perc_wait = round(Int,sum(isnothing.(protocol.patchPermutation))/size(protocol.patchPermutation,1)*100) est = "FG: $(timeFormat(fgTime)) ($(perc_wait)% waiting)\nBG: $(timeFormat(bgTime))" @info "The estimated duration is FG: $fgTime ($(perc_wait)% waiting), BG: $bgTime." diff --git a/src/Protocols/MultiSequenceSystemMatrixProtocol.jl b/src/Protocols/MultiSequenceSystemMatrixProtocol.jl index 8a4efa02..27d23a96 100644 --- a/src/Protocols/MultiSequenceSystemMatrixProtocol.jl +++ b/src/Protocols/MultiSequenceSystemMatrixProtocol.jl @@ -46,7 +46,7 @@ Base.@kwdef mutable struct MultiSequenceSystemMatrixProtocol <: Protocol done::Bool = false cancelled::Bool = false finishAcknowledged::Bool = false - stopped::Bool = false + paused::Bool = false restored::Bool = false measuring::Bool = false txCont::Union{TxDAQController,Nothing} = nothing @@ -68,6 +68,14 @@ function _init(protocol::MultiSequenceSystemMatrixProtocol) throw(IllegalStateException("Protocol requires sequences")) end + if any(acqNumFrameAverages.(protocol.params.sequences) .!= 1) + throw(ProtocolConfigurationError("The sequences for a MultiSequenceSystemMatrixProtocol currently do not support numFrameAverages != 1")) + end + + if length(protocol.params.sequences) != length(protocol.params.positions) + @warn "The MultiSequenceSystemMatrixProtocol has $(length(protocol.params.sequences)) sequences but you configured $(length(protocol.params.positions)) positions." + end + protocol.systemMeasState = SystemMatrixMeasState() numPos = length(protocol.params.sequences) measIsBGPos = [false for i = 1:numPos] @@ -112,7 +120,12 @@ function _init(protocol::MultiSequenceSystemMatrixProtocol) end function timeEstimate(protocol::MultiSequenceSystemMatrixProtocol) - est = "Unknown" + if protocol.params.waitTime > 5 + t = protocol.params.waitTime*length(protocol.params.sequences) * 1u"s" + est = timeFormat(t) + else + est = "Unknown" + end #if !isnothing(protocol.params.sequence) # params = protocol.params # seq = params.sequence @@ -127,7 +140,7 @@ function timeEstimate(protocol::MultiSequenceSystemMatrixProtocol) end function enterExecute(protocol::MultiSequenceSystemMatrixProtocol) - protocol.stopped = false + protocol.paused = false protocol.cancelled = false protocol.finishAcknowledged = false protocol.restored = false @@ -166,16 +179,16 @@ function _execute(protocol::MultiSequenceSystemMatrixProtocol) while !finished finished = performMeasurements(protocol) - # Stopped + # paused notifiedStop = false - while protocol.stopped + while protocol.paused handleEvents(protocol) protocol.cancelled && throw(CancelException()) if !notifiedStop - put!(protocol.biChannel, OperationSuccessfulEvent(StopEvent())) + put!(protocol.biChannel, OperationSuccessfulEvent(PauseEvent())) notifiedStop = true end - if !protocol.stopped + if !protocol.paused put!(protocol.biChannel, OperationSuccessfulEvent(ResumeEvent())) end sleep(0.05) @@ -199,15 +212,10 @@ function performMeasurements(protocol::MultiSequenceSystemMatrixProtocol) calib = protocol.systemMeasState while !finished - handleEvents(protocol) - - if protocol.stopped - enterPause(protocol) - finished = false - break - end wasRestored = protocol.restored + protocol.restored = false + timeWaited = @elapsed begin wait(calib.producer) wait(calib.consumer) @@ -215,7 +223,18 @@ function performMeasurements(protocol::MultiSequenceSystemMatrixProtocol) diffTime = protocol.params.waitTime - timeWaited if diffTime > 0.0 && !wasRestored && protocol.systemMeasState.currPos > 1 @info "Wait $diffTime s for next measurement" - sleep(diffTime) + for _ in 1:diffTime/0.1 + handleEvents(protocol) + if protocol.paused || protocol.cancelled + break + end + sleep(0.1) + end + end + if protocol.paused || protocol.cancelled + enterPause(protocol) + finished = false + break end performMeasurement(protocol) @@ -243,7 +262,7 @@ function performMeasurement(protocol::MultiSequenceSystemMatrixProtocol) # Prepare calib = protocol.systemMeasState index = calib.currPos - @info "Measurement $index of $(length(calib.positions))" + @info "Measurement $index of $(length(protocol.params.sequences))" daq = getDAQ(protocol.scanner) sequence = protocol.params.sequences[index] @@ -331,7 +350,7 @@ function store(protocol::MultiSequenceSystemMatrixProtocol, index) sysObj = protocol.systemMeasState params = MPIFiles.toDict(sysObj.positions) params["currPos"] = index + 1 # Safely stored up to and including index - #params["stopped"] = protocol.stopped + #params["paused"] = protocol.paused #params["currentSignal"] = sysObj.currentSignal params["waitTime"] = protocol.params.waitTime params["measIsBGPos"] = sysObj.measIsBGPos @@ -364,7 +383,7 @@ function restore(protocol::MultiSequenceSystemMatrixProtocol) if isfile(protocol, "meta.toml") params = TOML.parsefile(file(protocol, "meta.toml")) sysObj.currPos = params["currPos"] - protocol.stopped = false + protocol.paused = false protocol.params.waitTime = params["waitTime"] sysObj.measIsBGPos = params["measIsBGPos"] sysObj.posToIdx = params["posToIdx"] @@ -412,35 +431,32 @@ function restore(protocol::MultiSequenceSystemMatrixProtocol) end end - - function cleanup(protocol::MultiSequenceSystemMatrixProtocol) rm(dir(protocol), force = true, recursive = true) end -function stop(protocol::MultiSequenceSystemMatrixProtocol) +function pause(protocol::MultiSequenceSystemMatrixProtocol) calib = protocol.systemMeasState if calib.currPos <= length(calib.positions) # OperationSuccessfulEvent is put when it actually is in the stop loop - protocol.stopped = true + protocol.paused = true else - # Stopped has no concept once all measurements are done - put!(protocol.biChannel, OperationUnsuccessfulEvent(StopEvent())) + # paused has no concept once all measurements are done + put!(protocol.biChannel, OperationUnsuccessfulEvent(PauseEvent())) end end function resume(protocol::MultiSequenceSystemMatrixProtocol) - protocol.stopped = false + protocol.paused = false protocol.restored = true # OperationSuccessfulEvent is put when it actually leaves the stop loop end function cancel(protocol::MultiSequenceSystemMatrixProtocol) protocol.cancelled = true # Set cancel s.t. exception can be thrown when appropiate - protocol.stopped = true # Set stop to reach a known/save state + protocol.paused = true # Set stop to reach a known/save state end - function handleEvent(protocl::MultiSequenceSystemMatrixProtocol, event::ProgressQueryEvent) put!(protocl.biChannel, ProgressEvent(protocl.systemMeasState.currPos, length(protocl.params.sequences), "Position", event)) end diff --git a/src/Utils/Utils.jl b/src/Utils/Utils.jl index 6800f982..425dd4d4 100644 --- a/src/Utils/Utils.jl +++ b/src/Utils/Utils.jl @@ -14,4 +14,22 @@ end Base.convert(::Type{IPAddr}, str::AbstractString) = parse(IPAddr, str) # TODO: Remove this type piracy -Base.convert(::Type{ClusterTriggerSetup}, str::AbstractString) = stringToEnum(str, ClusterTriggerSetup) \ No newline at end of file +Base.convert(::Type{ClusterTriggerSetup}, str::AbstractString) = stringToEnum(str, ClusterTriggerSetup) + +# should be available in Unitful but isnt https://github.com/PainterQubits/Unitful.jl/issues/240 +function timeFormat(t::Unitful.Time) + v = ustrip(u"s",t) + if v>3600 + x = Int((v%3600)÷60) + return "$(Int(v÷3600)):$(if x<10; "0" else "" end)$(x) h" + elseif v>60 + x = round(v%60,digits=1) + return "$(Int(v÷60)):$(if x<10; "0" else "" end)$(x) min" + elseif v>0.5 + return "$(round(v,digits=2)) s" + elseif v>0.5e-3 + return "$(round(v*1e3,digits=2)) ms" + else + return "$(round(v*1e6,digits=2)) µs" + end +end \ No newline at end of file From 0a7470b56be49e4272d2b15f76f8091f7907c672 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Wed, 16 Oct 2024 19:21:55 +0200 Subject: [PATCH 128/168] include 5 --- src/Protocols/MultiSequenceSystemMatrixProtocol.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Protocols/MultiSequenceSystemMatrixProtocol.jl b/src/Protocols/MultiSequenceSystemMatrixProtocol.jl index 27d23a96..94a43bff 100644 --- a/src/Protocols/MultiSequenceSystemMatrixProtocol.jl +++ b/src/Protocols/MultiSequenceSystemMatrixProtocol.jl @@ -120,7 +120,7 @@ function _init(protocol::MultiSequenceSystemMatrixProtocol) end function timeEstimate(protocol::MultiSequenceSystemMatrixProtocol) - if protocol.params.waitTime > 5 + if protocol.params.waitTime >= 5 t = protocol.params.waitTime*length(protocol.params.sequences) * 1u"s" est = timeFormat(t) else From 07613218e11c469f3ad66b38e6b5208c3676c660 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Thu, 17 Oct 2024 09:26:08 +0200 Subject: [PATCH 129/168] fix typo --- src/Protocols/MultiSequenceSystemMatrixProtocol.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Protocols/MultiSequenceSystemMatrixProtocol.jl b/src/Protocols/MultiSequenceSystemMatrixProtocol.jl index 94a43bff..8df040ef 100644 --- a/src/Protocols/MultiSequenceSystemMatrixProtocol.jl +++ b/src/Protocols/MultiSequenceSystemMatrixProtocol.jl @@ -457,8 +457,8 @@ function cancel(protocol::MultiSequenceSystemMatrixProtocol) protocol.paused = true # Set stop to reach a known/save state end -function handleEvent(protocl::MultiSequenceSystemMatrixProtocol, event::ProgressQueryEvent) - put!(protocl.biChannel, ProgressEvent(protocl.systemMeasState.currPos, length(protocl.params.sequences), "Position", event)) +function handleEvent(protocol::MultiSequenceSystemMatrixProtocol, event::ProgressQueryEvent) + put!(protocol.biChannel, ProgressEvent(protocol.systemMeasState.currPos, length(protocol.params.sequences), "Position", event)) end function handleEvent(protocol::MultiSequenceSystemMatrixProtocol, event::DataQueryEvent) From 9eda0194be0eb07976594e41475ae53333ab61e8 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Thu, 17 Oct 2024 17:43:56 +0200 Subject: [PATCH 130/168] fixed remember BG in MPI protocol --- src/Protocols/MPIMeasurementProtocol.jl | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/Protocols/MPIMeasurementProtocol.jl b/src/Protocols/MPIMeasurementProtocol.jl index 44396b5e..220c8766 100644 --- a/src/Protocols/MPIMeasurementProtocol.jl +++ b/src/Protocols/MPIMeasurementProtocol.jl @@ -15,8 +15,8 @@ Base.@kwdef mutable struct MPIMeasurementProtocolParams <: ProtocolParams saveTemperatureData::Bool = false "Sequence to measure" sequence::Union{Sequence, Nothing} = nothing - "Remember background measurement" - rememberBGMeas::Bool = false + "Do not measure background but reuse the last BG measurement if suitable" + reuseLastBGMeas::Bool = false end function MPIMeasurementProtocolParams(dict::Dict, scanner::MPIScanner) sequence = nothing @@ -78,7 +78,7 @@ function timeEstimate(protocol::MPIMeasurementProtocol) if !isnothing(protocol.params.sequence) params = protocol.params seq = params.sequence - totalFrames = (params.fgFrames + params.bgFrames) * acqNumFrameAverages(seq) + totalFrames = (params.fgFrames + params.bgFrames*params.measureBackground*!params.reuseLastBGMeas) * acqNumFrameAverages(seq) samplesPerFrame = rxNumSamplingPoints(seq) * acqNumAverages(seq) * acqNumPeriodsPerFrame(seq) totalTime = (samplesPerFrame * totalFrames) / (125e6/(txBaseFrequency(seq)/rxSamplingRate(seq))) time = totalTime * 1u"s" @@ -114,7 +114,7 @@ function _execute(protocol::MPIMeasurementProtocol) end function performMeasurement(protocol::MPIMeasurementProtocol) - if (length(protocol.bgMeas) == 0 || !protocol.params.rememberBGMeas) && protocol.params.measureBackground + if (length(protocol.bgMeas) == 0 || !protocol.params.reuseLastBGMeas) && protocol.params.measureBackground if askChoices(protocol, "Press continue when background measurement can be taken", ["Cancel", "Continue"]) == 1 throw(CancelException()) end @@ -123,9 +123,9 @@ function performMeasurement(protocol::MPIMeasurementProtocol) @debug "Taking background measurement." protocol.unit = "BG Frames" measurement(protocol) - protocol.bgMeas = read(sink(protocol.seqMeasState.sequenceBuffer, MeasurementBuffer)) deviceBuffers = protocol.seqMeasState.deviceBuffers push!(protocol.protocolMeasState, vcat(sinks(protocol.seqMeasState.sequenceBuffer), isnothing(deviceBuffers) ? SinkBuffer[] : deviceBuffers), isBGMeas = true) + protocol.bgMeas = read(protocol.protocolMeasState, MeasurementBuffer) if askChoices(protocol, "Press continue when foreground measurement can be taken", ["Cancel", "Continue"]) == 1 throw(CancelException()) end @@ -256,6 +256,14 @@ function handleEvent(protocol::MPIMeasurementProtocol, event::DatasetStoreStorag mdf = event.mdf data = read(protocol.protocolMeasState, MeasurementBuffer) isBGFrame = measIsBGFrame(protocol.protocolMeasState) + if protocol.params.reuseLastBGMeas + try + data = cat(data,protocol.bgMeas,dims=4) + isBGFrame = cat(isBGFrame,trues(size(protocol.bgMeas,4)), dims=1) + catch + @error "The last BG measurement is not compatible with your current measurement, cant write BG into file! Data is size $(size(data)), BG is size $(size(protocol.bgMeas))" + end + end drivefield = read(protocol.protocolMeasState, DriveFieldBuffer) appliedField = read(protocol.protocolMeasState, TxDAQControllerBuffer) temperature = read(protocol.protocolMeasState, TemperatureBuffer) From 2be48bb1b66c5a9ba2641994a6b75e7e513a7645 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Thu, 28 Nov 2024 16:30:07 +0100 Subject: [PATCH 131/168] clean up --- src/Devices/DAQ/DAQ.jl | 10 +--- src/Devices/Virtual/TxDAQController.jl | 81 +++++++------------------- src/Utils/DictToStruct.jl | 2 +- 3 files changed, 22 insertions(+), 71 deletions(-) diff --git a/src/Devices/DAQ/DAQ.jl b/src/Devices/DAQ/DAQ.jl index 2fe53a34..f1c8f4c4 100644 --- a/src/Devices/DAQ/DAQ.jl +++ b/src/Devices/DAQ/DAQ.jl @@ -285,15 +285,7 @@ end calibration(dev::Device, channelID::AbstractString, frequencies) = calibration.([dev], [channelID], frequencies) calibration(daq::AbstractDAQ, channelID::AbstractString, frequency::Real) = calibration(daq, channel(daq, channelID), frequency) -function calibration(dev::Union{MPIScanner, AbstractDAQ}, channel::DAQTxChannelParams, frequency::Real) - cal = calibration(dev, channel) - if cal isa TransferFunction - return cal(frequency) - else - @warn "You requested a calibration for a specific frequency $frequency but the channel $channelID has no frequency dependent calibration value" - return cal - end -end +calibration(dev::Union{MPIScanner, AbstractDAQ}, channel::DAQTxChannelParams, frequency::Real) = calibration(dev, channel)(frequency) export applyForwardCalibration!, applyForwardCalibration diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index 9536aea1..1e9b4e20 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -53,8 +53,8 @@ end Base.@kwdef mutable struct TxDAQController <: VirtualDevice @add_device_fields TxDAQControllerParams - ref::Union{Array{Float32, 4}, Nothing} = nothing # TODO remove when done - cont::Union{Nothing, ControlSequence} = nothing # TODO remove when done + ref::Union{Array{Float32, 4}, Nothing} = nothing + cont::Union{Nothing, ControlSequence} = nothing startFrame::Int64 = 1 controlResults::OrderedDict{String, Union{typeof(1.0im*u"V/T"), Dict{Float64,typeof(1.0im*u"V/T")}}} = Dict{String, Union{typeof(1.0im*u"V/T"), Dict{Float64,typeof(1.0im*u"V/T")}}}() lastDCResults::Union{Vector{@NamedTuple{V::Vector{Float64}, B::Vector{Float64}}},Nothing} = nothing @@ -342,7 +342,7 @@ end ############## Top-Level functions for interacting with the controller ############################################################################### -function controlTx(txCont::TxDAQController, seq::Sequence, ::Nothing = nothing) +function controlTx(txCont::TxDAQController, seq::Sequence) if needsControlOrDecoupling(seq) daq = dependency(txCont, AbstractDAQ) setupRx(daq, seq) @@ -447,7 +447,6 @@ function controlTx(txCont::TxDAQController, control::ControlSequence) controlPhaseDone = controlStep!(control, txCont, Γ, Ω) == UNCHANGED if controlPhaseDone @info "Could control" - # TODO/JA: extract control results as new calibration here updateCachedCalibration(txCont, control) else @info "Could not control" @@ -523,7 +522,6 @@ function getControlResult(cont::ControlSequence)::Sequence safeTrans = safeTransitionInterval(field) safeEnd = safeEndInterval(field) safeError = safeErrorInterval(field) - #TODO/JA: should the channels be a copy? Is it even necessary to create a new object just to set control to false? Maybe this will work anyways contField = MagneticField(;id = _id, channels = deepcopy(channels(field)), safeStartInterval = safeStart, safeTransitionInterval = safeTrans, safeEndInterval = safeEnd, safeErrorInterval = safeError, decouple = false, control = false) push!(_fields, contField) @@ -531,7 +529,7 @@ function getControlResult(cont::ControlSequence)::Sequence for field in fields(cont.targetSequence) if !control(field) push!(_fields, field) - # if there are LUT channels sharing a channel with the controlled fields, we should be able to use the DC calibration that has been found + # TODO/JA: if there are LUT channels sharing a channel with the controlled fields, we should be able to use the DC calibration that has been found # and insert it into the corresponding LUT channels as a calibration value end end @@ -668,12 +666,12 @@ end #updateControl!(cont::ControlSequence, txCont::TxDAQController, uRef) = updateControl!(cont, txCont, calcFieldFromRef(cont, uRef), calcDesiredField(cont)) function updateControl!(cont::ControlSequence, txCont::TxDAQController, Γ::Matrix{<:Complex}, Ω::Matrix{<:Complex}) @debug "Updating control values" - κ = calcControlMatrix(cont) + oldTx = calcControlMatrix(cont) - if !validateAgainstForwardCalibrationAndSafetyLimit(κ, Γ, cont, txCont) + if !validateAgainstForwardCalibrationAndSafetyLimit(oldTx, Γ, cont, txCont) error("Last control step produced unexpected results! Either your forward calibration is inaccurate or the system is not in the expected state (e.g. amp not on)!" ) end - newTx = updateControlMatrix(cont, txCont, Γ, Ω, κ) + newTx = updateControlMatrix(cont, txCont, Γ, Ω, oldTx) if validateAgainstForwardCalibrationAndSafetyLimit(newTx, Ω, cont, txCont) && checkVoltLimits(newTx, cont) updateControlSequence!(cont, newTx) @@ -686,29 +684,29 @@ end # Γ: Matrix from Ref # Ω: Desired Matrix -# κ: Last Set Matrix -function updateControlMatrix(cont::CrossCouplingControlSequence, txCont::TxDAQController, Γ::Matrix{<:Complex}, Ω::Matrix{<:Complex}, κ::Matrix{<:Complex}) +# oldTx: Last Set Matrix +function updateControlMatrix(cont::CrossCouplingControlSequence, txCont::TxDAQController, Γ::Matrix{<:Complex}, Ω::Matrix{<:Complex}, oldTx::Matrix{<:Complex}) if needsDecoupling(cont.targetSequence) - β = Γ*inv(κ) + β = Γ*inv(oldTx) else - β = diagm(diag(Γ))*inv(diagm(diag(κ))) + β = diagm(diag(Γ))*inv(diagm(diag(oldTx))) end newTx = inv(β)*Ω - @debug "Last TX matrix [V]:" κ + @debug "Last TX matrix [V]:" oldTx @debug "Ref matrix [T]:" Γ @debug "Desired matrix [T]:" Ω @debug "New TX matrix [V]:" newTx return newTx end -function updateControlMatrix(cont::AWControlSequence, txCont::TxDAQController, Γ::Matrix{<:Complex}, Ω::Matrix{<:Complex}, κ::Matrix{<:Complex}) +function updateControlMatrix(cont::AWControlSequence, txCont::TxDAQController, Γ::Matrix{<:Complex}, Ω::Matrix{<:Complex}, oldTx::Matrix{<:Complex}) # For now we completely ignore coupling and hope that it can find good values anyways # The problem is, that to achieve 0 we will always output zero, but we would need a much more sophisticated method to solve this - newTx = κ./Γ.*Ω + newTx = oldTx./Γ.*Ω # handle DC separately: if txCont.params.controlDC - push!(cont.dcSearch, (V=κ[:,1], B=Γ[:,1])) + push!(cont.dcSearch, (V=oldTx[:,1], B=Γ[:,1])) if length(cont.dcSearch)==1 my_sign(x) = if x<0; -1 else 1 end testOffset = real.(Ω[:,1])*u"T" .- 2u"mT"*my_sign.(real.(Ω[:,1])) @@ -721,7 +719,7 @@ function updateControlMatrix(cont::AWControlSequence, txCont::TxDAQController, end end - #@debug "Last TX matrix [V]:" κ=lineplot(1:rxNumSamplingPoints(cont.currSequence),checkVoltLimits(κ,cont,return_time_signal=true)') + #@debug "Last TX matrix [V]:" oldTx=lineplot(1:rxNumSamplingPoints(cont.currSequence),checkVoltLimits(oldTx,cont,return_time_signal=true)') #@debug "Ref matrix [T]:" Γ=lineplot(1:rxNumSamplingPoints(cont.currSequence),checkVoltLimits(Γ,cont,return_time_signal=true)') #@debug "Desired matrix [V]:" Ω=lineplot(1:rxNumSamplingPoints(cont.currSequence),checkVoltLimits(Ω,cont,return_time_signal=true)') #@debug "New TX matrix [T]:" newTx=lineplot(1:rxNumSamplingPoints(cont.currSequence),checkVoltLimits(newTx,cont,return_time_signal=true)') @@ -831,13 +829,13 @@ end # Convert Last Tx (currSequence) to Matrix in V function calcControlMatrix(cont::CrossCouplingControlSequence) - κ = zeros(ComplexF64, controlMatrixShape(cont)) + oldTx = zeros(ComplexF64, controlMatrixShape(cont)) for (i, channel) in enumerate(getControlledChannels(cont)) for (j, comp) in enumerate(periodicElectricalComponents(channel)) - κ[i, j] = ustrip(u"V", amplitude(comp)) * exp(im*ustrip(u"rad", phase(comp))) + oldTx[i, j] = ustrip(u"V", amplitude(comp)) * exp(im*ustrip(u"rad", phase(comp))) end end - return κ + return oldTx end function calcControlMatrix(cont::AWControlSequence) @@ -919,50 +917,11 @@ function validateAgainstForwardCalibrationAndSafetyLimit(tx::Matrix{<:Complex}, # step 3 check if B_fw and B are both below safety limit isSafe(Btest) = abs.(Btest). 10/180*pi - @warn "The phase of the measured field to volt deviates by $phase_deviation from estimate. Please check you phases! Continuing anyways..." - end - return valid -end - -function checkFieldToVolt(oldTx::Matrix{<:Complex}, Γ::Matrix{<:Complex}, cont::AWControlSequence, txCont::TxDAQController, Ω::Matrix{<:Complex}) - N = rxNumSamplingPoints(cont.currSequence) - frequencies = ustrip.(u"Hz",rfftfreq(N, rxSamplingRate(cont.currSequence))) - calibFieldToVoltEstimate = reduce(vcat,transpose([ustrip.(u"V/T", chan.calibration(frequencies)) for chan in getControlledDAQChannels(cont)])) - calibFieldToVoltMeasured = oldTx ./ Γ - - mask = allComponentMask(cont) .& (abs.(Ω).>1e-15) - abs_deviation = abs.(1.0 .- abs.(calibFieldToVoltMeasured[mask])./abs.(calibFieldToVoltEstimate[mask])) - phase_deviation = angle.(calibFieldToVoltMeasured[mask]) .- angle.(calibFieldToVoltEstimate[mask]) - @debug "checkFieldToVolt: We expected $(calibFieldToVoltEstimate[mask]) V/T and got $(calibFieldToVoltMeasured[mask]) V/T, deviation: $abs_deviation" - valid = maximum( abs_deviation ) < txCont.params.fieldToVoltRelDeviation - if !valid - @error "Measured field to volt deviates by $(abs_deviation*100) % from estimate, exceeding allowed deviation of $(txCont.params.fieldToVoltRelDeviation*100) %" - elseif maximum(abs.(phase_deviation)) > 10/180*pi - @warn "The phase of the measured field to volt deviates by $phase_deviation from estimate. Please check you phases! Continuing anyways..." - end - return valid -end - - function checkVoltLimits(newTx::Matrix{<:Complex}, cont::CrossCouplingControlSequence) validChannel = zeros(Bool, size(newTx, 1)) for i = 1:size(newTx, 1) diff --git a/src/Utils/DictToStruct.jl b/src/Utils/DictToStruct.jl index 8618699d..063c762b 100644 --- a/src/Utils/DictToStruct.jl +++ b/src/Utils/DictToStruct.jl @@ -51,7 +51,7 @@ function parse_into_tf(value::String) calibration_tf = value else # case 2: single value, extended into transfer function with no frequency dependency calibration_value = uparse(value) - calibration_tf = TransferFunction([0,10e6],ComplexF64[ustrip(calibration_value), ustrip(calibration_value)], units=[unit(calibration_value)]) + calibration_tf = TransferFunction([0,Inf],ComplexF64[ustrip(calibration_value), ustrip(calibration_value)], units=[unit(calibration_value)]) end return calibration_tf end From 5afc32c7c385eb22346d3e775dc99b8f307c03a6 Mon Sep 17 00:00:00 2001 From: nHackel Date: Fri, 6 Dec 2024 15:55:39 +0100 Subject: [PATCH 132/168] Parse channel selection of transferfucntion in _init --- src/Devices/DAQ/DAQ.jl | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Devices/DAQ/DAQ.jl b/src/Devices/DAQ/DAQ.jl index f1c8f4c4..26592402 100644 --- a/src/Devices/DAQ/DAQ.jl +++ b/src/Devices/DAQ/DAQ.jl @@ -264,11 +264,20 @@ feedbackTransferFunction(daq::AbstractDAQ, channelID::AbstractString) = feedback hasTransferFunction(channel::DAQRxChannelParams) = !isnothing(channel.transferFunction) hasTransferFunction(daq::AbstractDAQ, channelID::AbstractString) = hasTransferFunction(channel(daq,channelID)) +function splitTFName(name::AbstractString) + if endswith(name, r"\.h5:\d+") + tmp = rsplit(name, ":"; limit=2) + return tmp[1], parse(Int, tmp[2]) + else + return name, nothing + end +end transferFunction(::AbstractDAQ, ::Nothing) = nothing transferFunction(daq::AbstractDAQ, channelID::AbstractString) = transferFunction(daq, channel(daq, channelID)) function transferFunction(dev::Union{MPIScanner, AbstractDAQ}, channel::DAQRxChannelParams) if isa(channel.transferFunction, String) - channel.transferFunction = TransferFunction(joinpath(configDir(dev), "TransferFunctions", channel.transferFunction)) + filename, channel = splitTFName(channel.transferFunction) + channel.transferFunction = TransferFunction(joinpath(configDir(dev), "TransferFunctions", filename), channels = channel) else channel.transferFunction end @@ -277,7 +286,8 @@ end calibration(daq::AbstractDAQ, channelID::AbstractString) = calibration(daq, channel(daq,channelID)) function calibration(dev::Union{MPIScanner, AbstractDAQ}, channel::DAQTxChannelParams) if isa(channel.calibration, String) - channel.calibration = TransferFunction(joinpath(configDir(dev), "TransferFunctions", channel.calibration)) + filename, channel = splitTFName(channel.calibration) + channel.calibration = TransferFunction(joinpath(configDir(dev), "TransferFunctions", filename), channels = channel) else channel.calibration end From 2ace594b17759b3f32f07a90807c96c6e1d09368 Mon Sep 17 00:00:00 2001 From: Justin <38977690+jusack@users.noreply.github.com> Date: Tue, 17 Dec 2024 12:18:24 +0100 Subject: [PATCH 133/168] Update Project.toml --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 19fe52fb..ac5d3cf7 100644 --- a/Project.toml +++ b/Project.toml @@ -40,7 +40,7 @@ HDF5 = "0.16" InteractiveUtils = "1" LibSerialPort = "0.5.2" LinearAlgebra = "1" -MPIFiles = "0.15,0.16" +MPIFiles = "0.15,0.16,0.17" MacroTools = "0.5.6" Mmap = "1" NaturalSort = "1" From 4690d05533e9543782e2b9347ce59ecea51ea298 Mon Sep 17 00:00:00 2001 From: Justin <38977690+jusack@users.noreply.github.com> Date: Wed, 18 Dec 2024 08:21:41 +0100 Subject: [PATCH 134/168] fix duplicate variable name --- src/Devices/DAQ/DAQ.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Devices/DAQ/DAQ.jl b/src/Devices/DAQ/DAQ.jl index 26592402..dd275d2a 100644 --- a/src/Devices/DAQ/DAQ.jl +++ b/src/Devices/DAQ/DAQ.jl @@ -276,8 +276,8 @@ transferFunction(::AbstractDAQ, ::Nothing) = nothing transferFunction(daq::AbstractDAQ, channelID::AbstractString) = transferFunction(daq, channel(daq, channelID)) function transferFunction(dev::Union{MPIScanner, AbstractDAQ}, channel::DAQRxChannelParams) if isa(channel.transferFunction, String) - filename, channel = splitTFName(channel.transferFunction) - channel.transferFunction = TransferFunction(joinpath(configDir(dev), "TransferFunctions", filename), channels = channel) + filename, tfChannel = splitTFName(channel.transferFunction) + channel.transferFunction = TransferFunction(joinpath(configDir(dev), "TransferFunctions", filename), channels = tfChannel) else channel.transferFunction end From 74967ad76004f8a19d1ba72b708dc13564c5a29d Mon Sep 17 00:00:00 2001 From: Justin <38977690+jusack@users.noreply.github.com> Date: Wed, 18 Dec 2024 08:43:01 +0100 Subject: [PATCH 135/168] output control result as info --- src/Devices/Virtual/TxDAQController.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index 1e9b4e20..36a918ad 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -542,7 +542,7 @@ setup(daq::AbstractDAQ, sequence::ControlSequence) = setup(daq, getControlResult function updateCachedCalibration(txCont::TxDAQController, cont::ControlSequence) finalCalibration = calcControlMatrix(cont) ./ calcDesiredField(cont) calibrationResults = finalCalibration[.!isnan.(finalCalibration)] - @debug "Control result: You could update the forward calibration in the Scanner.toml to this value for faster control" calibrationResults + @info "Control result: You could update the forward calibration in the Scanner.toml to this value for faster control" calibrationResults nothing end function updateCachedCalibration(txCont::TxDAQController, cont::AWControlSequence) From 0f1f9d8187136fd3621edf4f0e20013adabb1d29 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Wed, 18 Dec 2024 12:28:14 +0100 Subject: [PATCH 136/168] fix variable name --- src/Devices/DAQ/DAQ.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Devices/DAQ/DAQ.jl b/src/Devices/DAQ/DAQ.jl index dd275d2a..6edf0a4c 100644 --- a/src/Devices/DAQ/DAQ.jl +++ b/src/Devices/DAQ/DAQ.jl @@ -286,8 +286,8 @@ end calibration(daq::AbstractDAQ, channelID::AbstractString) = calibration(daq, channel(daq,channelID)) function calibration(dev::Union{MPIScanner, AbstractDAQ}, channel::DAQTxChannelParams) if isa(channel.calibration, String) - filename, channel = splitTFName(channel.calibration) - channel.calibration = TransferFunction(joinpath(configDir(dev), "TransferFunctions", filename), channels = channel) + filename, tfChannel = splitTFName(channel.calibration) + channel.calibration = TransferFunction(joinpath(configDir(dev), "TransferFunctions", filename), channels = tfChannel) else channel.calibration end From cb76ff7d360bb3dc14a56a68ec4977f01c04a00a Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Fri, 20 Dec 2024 10:54:34 +0100 Subject: [PATCH 137/168] write frame averages to MDF as well --- src/Protocols/Storage/MDF.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Protocols/Storage/MDF.jl b/src/Protocols/Storage/MDF.jl index 15ec700a..f1521eb0 100644 --- a/src/Protocols/Storage/MDF.jl +++ b/src/Protocols/Storage/MDF.jl @@ -299,7 +299,7 @@ function fillMDFAcquisition(mdf::MDFv2InMemory, scanner::MPIScanner, sequence::S #acqGradient(mdf, acqGradient(sequence)) # TODO: Impelent in Sequence #MPIFiles.acqGradient(mdf, ustrip.(u"T/m", scannerGradient(scanner))) - MPIFiles.acqNumAverages(mdf, acqNumAverages(sequence)) + MPIFiles.acqNumAverages(mdf, acqNumAverages(sequence)*acqNumFrameAverages(sequence)) MPIFiles.acqNumFrames(mdf, length(measIsBackgroundFrame(mdf))) # important since we might have added BG frames MPIFiles.acqNumPeriodsPerFrame(mdf, size(measData(mdf), 3)) # Hacky to support cases where periods of data != periods of sequence offsetField_ = acqOffsetField(sequence) From 1145e30af4e606b37826ef74aaeb9c6e7fa98851 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Fri, 20 Dec 2024 10:55:07 +0100 Subject: [PATCH 138/168] add caching to cross coupling control sequence --- src/Devices/DAQ/RedPitayaDAQ.jl | 2 -- src/Devices/Virtual/TxDAQController.jl | 20 ++++++++++++++++---- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index 655000a6..5c51ea64 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -299,8 +299,6 @@ function setSequenceParams(daq::RedPitayaDAQ, luts::Vector{Union{Nothing, Array{ @debug "There are no LUTs to set." end - @info "Set sequence params" - stepsPerRepetition = div(periodsPerFrame(daq.rpc), daq.acqPeriodsPerPatch) @debug "Set sequence params" samplesPerStep=div(samplesPerPeriod(daq.rpc) * periodsPerFrame(daq.rpc), stepsPerRepetition) result = execute!(daq.rpc) do batch diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index 36a918ad..827a96b4 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -540,16 +540,28 @@ end setup(daq::AbstractDAQ, sequence::ControlSequence) = setup(daq, getControlResult(sequence)) function updateCachedCalibration(txCont::TxDAQController, cont::ControlSequence) - finalCalibration = calcControlMatrix(cont) ./ calcDesiredField(cont) - calibrationResults = finalCalibration[.!isnan.(finalCalibration)] - @info "Control result: You could update the forward calibration in the Scanner.toml to this value for faster control" calibrationResults + finalCalibration = diag(calcControlMatrix(cont) ./ calcDesiredField(cont)) + channelIds = id.(getControlledChannels(cont)) + dividers = divider.(MPIMeasurements.getPrimaryComponents(cont)) + frequencies = ustrip(u"Hz", txBaseFrequency(cont.currSequence)) ./ dividers + for i in axes(finalCalibration, 1) + if !isnan(finalCalibration[i]) + if !haskey(txCont.controlResults, channelIds[i]) + txCont.controlResults[channelIds[i]] = Dict{Float64,typeof(1.0im*u"V/T")}() + end + txCont.controlResults[channelIds[i]][frequencies[i]] = finalCalibration[i]*u"V/T" + @debug "Cached control result: $(round(finalCalibration[i], digits,2)*u"V/T") for channel $(channelIds[i]) at $(round(frequencies[i],digits=3)) Hz" + end + end + nothing end + function updateCachedCalibration(txCont::TxDAQController, cont::AWControlSequence) finalCalibration = calcControlMatrix(cont) ./ calcDesiredField(cont) calibrationResults = findall(x->!isnan(x), finalCalibration) - channelIDs = id.(keys(cont.controlledChannelsDict)) + channelIDs = id.(getControlledChannels(cont)) freqAxis = rfftfreq(rxNumSamplingPoints(cont.currSequence),ustrip(u"Hz",2*rxBandwidth(cont.currSequence))) for res in calibrationResults From 30c58cb8fcb92611341eefe5fd928c833a4a334e Mon Sep 17 00:00:00 2001 From: jonschumacher Date: Fri, 20 Dec 2024 12:47:38 +0100 Subject: [PATCH 139/168] fix error message --- src/Devices/Virtual/TxDAQController.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index 827a96b4..5cc73584 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -550,7 +550,7 @@ function updateCachedCalibration(txCont::TxDAQController, cont::ControlSequence) txCont.controlResults[channelIds[i]] = Dict{Float64,typeof(1.0im*u"V/T")}() end txCont.controlResults[channelIds[i]][frequencies[i]] = finalCalibration[i]*u"V/T" - @debug "Cached control result: $(round(finalCalibration[i], digits,2)*u"V/T") for channel $(channelIds[i]) at $(round(frequencies[i],digits=3)) Hz" + @debug "Cached control result: $(round(finalCalibration[i], digits=2)*u"V/T") for channel $(channelIds[i]) at $(round(frequencies[i],digits=3)) Hz" end end From 4da4fd21aaf2eb98e6c6d2243d04f347c51793c9 Mon Sep 17 00:00:00 2001 From: Jonas Schumacher Date: Mon, 5 May 2025 14:08:30 +0200 Subject: [PATCH 140/168] Make offsets always 3D when saving (#81) --- src/Protocols/MPSMeasurementProtocol.jl | 11 +++++++++-- src/Protocols/Storage/MDF.jl | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/Protocols/MPSMeasurementProtocol.jl b/src/Protocols/MPSMeasurementProtocol.jl index 24a14c52..8c243053 100644 --- a/src/Protocols/MPSMeasurementProtocol.jl +++ b/src/Protocols/MPSMeasurementProtocol.jl @@ -431,9 +431,16 @@ function handleEvent(protocol::MPSMeasurementProtocol, event::DatasetStoreStorag periodsPerOffset = protocol.params.averagePeriodsPerOffset ? 1 : protocol.params.dfPeriodsPerOffset isBGFrame = repeat(isBGFrame, inner = div(size(data, 3), periodsPerOffset)) data = reshape(data, size(data, 1), size(data, 2), periodsPerOffset, :) + # All periods in one frame (should) have same offset - offsets = reshape(offsets, periodsPerOffset, :, size(offsets, 2))[1, :, :] - offsets = reshape(offsets, protocol.calibsize..., :) # make calib size "visible" to storing function + offsets = reshape(offsets, (periodsPerOffset, :, size(offsets, 2)))[1, :, :] + + # Pad zeros in z or z and y direction if the protocol uses less than 3 offset dimensions. + @assert size(offsets, 2) <= 3 "The number of offset dimensions must be below or equal to 3 since the MDF specifies Ox3 with directions in x, y and z." + offsets = hcat(offsets, zeros(eltype(offsets), (size(offsets, 1), 3 - size(offsets, 2)))) + + calibsize_ = vcat(protocol.calibsize, ones(eltype(protocol.calibsize), 3 - length(protocol.calibsize))) + offsets = reshape(offsets, (calibsize_..., :)) # make calib size "visible" to storing function filename = saveasMDF(store, scanner, sequence, data, offsets, isBGFrame, mdf, storeAsSystemMatrix=protocol.params.saveInCalibFolder, drivefield = drivefield, temperatures = temperature, applied = appliedField) @info "The measurement was saved at `$filename`." diff --git a/src/Protocols/Storage/MDF.jl b/src/Protocols/Storage/MDF.jl index f504f664..0e25651d 100644 --- a/src/Protocols/Storage/MDF.jl +++ b/src/Protocols/Storage/MDF.jl @@ -152,7 +152,7 @@ function fillMDFCalibration(mdf::MDFv2InMemory, offsetFields::Union{AbstractArra mdf.calibration = MDFv2Calibration(; deltaSampleSize = deltaSampleSize, method = method, - offsetFields = offsetFields, + offsetFields = permutedims(offsetFields), # Switch dimensions since the MDF specifies Ox3 but Julia is column major order = order, size = collect(calibsize) ) From 4d783272d892d4666b4d46a10d1aa59662a5acb1 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Tue, 20 May 2025 14:01:09 +0200 Subject: [PATCH 141/168] fix averaging for MPS Protocol --- src/Protocols/MPSMeasurementProtocol.jl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Protocols/MPSMeasurementProtocol.jl b/src/Protocols/MPSMeasurementProtocol.jl index be1228e6..ce791631 100644 --- a/src/Protocols/MPSMeasurementProtocol.jl +++ b/src/Protocols/MPSMeasurementProtocol.jl @@ -275,14 +275,14 @@ function SequenceMeasState(protocol::MPSMeasurementProtocol) buffer = FrameBuffer(protocol, "meas.bin", Float32, bufferSize) # buffers = StorageBuffer[buffer] - - if protocol.params.controlTx + # TODO: reenable DriveFieldBuffer + #if protocol.params.controlTx #len = length(keys(sequence.refIndices)) - push!(buffers, DriveFieldBuffer(1, zeros(ComplexF64, controlMatrixShape(sequence)..., 1, numFrames), sequence)) - end + # push!(buffers, DriveFieldBuffer(1, zeros(ComplexF64, controlMatrixShape(sequence)..., 1, numFrames), sequence)) + #end # buffer = FrameSplitterBuffer(daq, StorageBuffer[buffer]) - buffer = MPSBuffer(buffer, protocol.patchPermutation, numFrames, 1, acqNumPeriodsPerFrame(protocol.sequence)) + buffer = MPSBuffer(buffer, protocol.patchPermutation, averages, 1, acqNumPeriodsPerFrame(protocol.sequence)) channel = Channel{channelType(daq)}(32) deviceBuffer = DeviceBuffer[] From c33e790d69b134d92eb7eb12398f16e879c2b09b Mon Sep 17 00:00:00 2001 From: Jonas Schumacher Date: Tue, 10 Jun 2025 14:15:55 +0200 Subject: [PATCH 142/168] hack DF defined in volt into MDF --- src/Sequences/Sequence.jl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Sequences/Sequence.jl b/src/Sequences/Sequence.jl index a9f45d03..5a262276 100644 --- a/src/Sequences/Sequence.jl +++ b/src/Sequences/Sequence.jl @@ -423,6 +423,11 @@ function dfStrength(sequence::Sequence) # TODO: How do we integrate the mechanic for (channelIdx, channel) in enumerate(channels) for (componentIdx, component) in enumerate(channel.components) for (periodIdx, strength) in enumerate(amplitude(component)) # TODO: What do we do if this is in volt? The conversion factor is with the scanner... Remove the volt version? + if strength isa Unitful.Voltage + # HACKY way to save Voltage DFs into the MDF -> use Tesla as mV (everything > 1 will be V) + @warn "Your DF is defined as a voltage! Will save $(uconvert(u"mV",strength)) as $(ustrip(u"mV", strength).*u"T")" + strength = ustrip(u"mV", strength).*u"T" + end result[periodIdx, channelIdx, componentIdx] = strength end end From e0f1c5310ac44ca9f172b1f1d4d045a57973f3ed Mon Sep 17 00:00:00 2001 From: nHackel Date: Wed, 18 Jun 2025 14:47:05 +0200 Subject: [PATCH 143/168] Make default scanner folder relocatable --- Project.toml | 8 +++++--- src/MPIMeasurements.jl | 7 ++++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Project.toml b/Project.toml index ac5d3cf7..e2529b8d 100644 --- a/Project.toml +++ b/Project.toml @@ -19,6 +19,7 @@ NaturalSort = "c020b1a1-e9b0-503a-9c33-f039bfc54a85" ProgressMeter = "92933f4c-e287-5a05-a399-4b506db050ca" REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" RedPitayaDAQServer = "c544963a-496b-56d4-a5fe-f99a3f174c8f" +RelocatableFolders = "05181044-ff0b-4ac5-8273-598c1e38db00" ReplMaker = "b873ce64-0db9-51f5-a568-4457d8e49576" Scratch = "6c6a2e73-6563-6170-7368-637461726353" Sockets = "6462fe0b-24de-5631-8697-dd941f90decc" @@ -48,18 +49,19 @@ Pkg = "1" Plots = "1.39" ProgressMeter = "1.5" REPL = "1" -RedPitayaDAQServer = "0.6, 0.7, 0.8" +RedPitayaDAQServer = "0.6, 0.7, 0.8, 0.9, 0.10" +RelocatableFolders = "1" ReplMaker = "0.2.7" Scratch = "1.1.0" Sockets = "1" Statistics = "1" StringEncodings = "0.3.5" -Test = "1" TOML = "1" +Test = "1" ThreadPools = "2.1.1" UUIDs = "1" -Unitful = "1.13, 1.14, 1.15, 1.16, 1.17" UnicodePlots = "3" +Unitful = "1.13, 1.14, 1.15, 1.16, 1.17" julia = "1.10" [extras] diff --git a/src/MPIMeasurements.jl b/src/MPIMeasurements.jl index e847f84b..3ec53470 100644 --- a/src/MPIMeasurements.jl +++ b/src/MPIMeasurements.jl @@ -24,6 +24,7 @@ using LinearAlgebra using HDF5 using FFTW using NaturalSort +using RelocatableFolders using ReplMaker import REPL @@ -44,11 +45,11 @@ import MPIFiles: hasKeyAndValue, rxBandwidth, rxNumChannels, rxNumSamplingPoints using RedPitayaDAQServer - -const scannerConfigurationPath = [normpath(string(@__DIR__), "../config")] # Push custom configuration directories here +const DEFAULT_SCANNER_CONFIG_PATH = @path joinpath(@__DIR__, "..", "config") +const scannerConfigurationPath = AbstractString[DEFAULT_SCANNER_CONFIG_PATH] # Push custom configuration directories here export addConfigurationPath -addConfigurationPath(path::String) = !(path in scannerConfigurationPath) ? pushfirst!(scannerConfigurationPath, path) : nothing +addConfigurationPath(path) = !(path in scannerConfigurationPath) ? pushfirst!(scannerConfigurationPath, path) : nothing # Circular reference between Scanner.jl and Protocol.jl. Thus we predefine the protocol """ From b26dc4e932877335b2a7fa5e3acff2c545c86048 Mon Sep 17 00:00:00 2001 From: Jonas Schumacher Date: Wed, 25 Jun 2025 14:40:52 +0200 Subject: [PATCH 144/168] fix meas view during acquisition --- src/Protocols/RobotBasedSystemMatrixProtocol.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Protocols/RobotBasedSystemMatrixProtocol.jl b/src/Protocols/RobotBasedSystemMatrixProtocol.jl index ce255a7d..87e78e2d 100644 --- a/src/Protocols/RobotBasedSystemMatrixProtocol.jl +++ b/src/Protocols/RobotBasedSystemMatrixProtocol.jl @@ -502,7 +502,7 @@ end function handleEvent(protocol::RobotBasedSystemMatrixProtocol, event::DataQueryEvent) data = nothing - if event.message == "CURR" + if event.message == "SIGNAL" data = protocol.systemMeasState.currentSignal elseif event.message == "BG" sysObj = protocol.systemMeasState From 0d33a06c7e97245f73f474c1bdcbf87c21530e38 Mon Sep 17 00:00:00 2001 From: Jonas Schumacher Date: Wed, 25 Jun 2025 14:41:33 +0200 Subject: [PATCH 145/168] make SU optional --- src/Protocols/RobotBasedSystemMatrixProtocol.jl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Protocols/RobotBasedSystemMatrixProtocol.jl b/src/Protocols/RobotBasedSystemMatrixProtocol.jl index 87e78e2d..700fa85c 100644 --- a/src/Protocols/RobotBasedSystemMatrixProtocol.jl +++ b/src/Protocols/RobotBasedSystemMatrixProtocol.jl @@ -86,7 +86,7 @@ function SystemMatrixMeasState() end function requiredDevices(protocol::RobotBasedSystemMatrixProtocol) - result = [AbstractDAQ, Robot, SurveillanceUnit] + result = [AbstractDAQ, Robot] if protocol.params.controlTx push!(result, TxDAQController) end @@ -289,7 +289,9 @@ function postMovement(protocol::RobotBasedSystemMatrixProtocol) channelIdx = id.(vcat(acyclicElectricalTxChannels(protocol.params.sequence), periodicElectricalTxChannels(protocol.params.sequence))) amps = filter(amp -> in(channelId(amp), channelIdx), amps) end +if !isnothing(su) enableACPower(su) +end if tempControl != nothing disableControl(tempControl) end @@ -326,7 +328,9 @@ function postMovement(protocol::RobotBasedSystemMatrixProtocol) if tempControl != nothing enableControl(tempControl) end +if !isnothing(su) disableACPower(su) +end end end From 10dfa6a8a588b094f4ac41c222fdd6b8e6c69b04 Mon Sep 17 00:00:00 2001 From: Jonas Schumacher Date: Wed, 25 Jun 2025 14:43:09 +0200 Subject: [PATCH 146/168] allow frameAveraging in SM --- .../RobotBasedSystemMatrixProtocol.jl | 35 ++++++++++++------- src/Protocols/Storage/ProducerConsumer.jl | 2 +- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/src/Protocols/RobotBasedSystemMatrixProtocol.jl b/src/Protocols/RobotBasedSystemMatrixProtocol.jl index 700fa85c..dcd08fea 100644 --- a/src/Protocols/RobotBasedSystemMatrixProtocol.jl +++ b/src/Protocols/RobotBasedSystemMatrixProtocol.jl @@ -271,7 +271,7 @@ function prepareDAQ(protocol::RobotBasedSystemMatrixProtocol) acqNumFrames(sequence, calib.measIsBGPos[calib.currPos] ? protocol.params.bgFrames : protocol.params.fgFrames) #acqNumFrameAverages(sequence, calib.measIsBGPos[calib.currPos] ? 1 : protocol.params.fgFrames) - acqNumFrameAverages(sequence, 1) + #acqNumFrameAverages(sequence, 1) setup(daq, sequence) end @@ -289,9 +289,9 @@ function postMovement(protocol::RobotBasedSystemMatrixProtocol) channelIdx = id.(vcat(acyclicElectricalTxChannels(protocol.params.sequence), periodicElectricalTxChannels(protocol.params.sequence))) amps = filter(amp -> in(channelId(amp), channelIdx), amps) end -if !isnothing(su) - enableACPower(su) -end + if !isnothing(su) + enableACPower(su) + end if tempControl != nothing disableControl(tempControl) end @@ -328,9 +328,9 @@ end if tempControl != nothing enableControl(tempControl) end -if !isnothing(su) - disableACPower(su) -end + if !isnothing(su) + disableACPower(su) + end end end @@ -343,19 +343,30 @@ function asyncConsumer(channel::Channel, protocol::RobotBasedSystemMatrixProtoco stopIdx = startIdx + numFrames - 1 # Prepare Buffer - deviceBuffer = DeviceBuffer[] + deviceBuffer = StorageBuffer[] if protocol.params.saveTemperatureData tempSensor = getTemperatureSensor(protocol.scanner) push!(deviceBuffer, TemperatureBuffer(view(calib.temperatures, :, startIdx:stopIdx), tempSensor)) end sinks = StorageBuffer[] - push!(sinks, FrameBuffer(1, view(calib.signals, :, :, :, startIdx:stopIdx))) + buffer = FrameBuffer(1, view(calib.signals, :, :, :, startIdx:stopIdx)) + if acqNumFrameAverages(protocol.params.sequence) > 1 + buffer = AverageBuffer(buffer, protocol.params.sequence) + end + push!(sinks, buffer) + sequence = protocol.params.sequence if protocol.params.controlTx - sequence = protocol.contSequence - push!(sinks, DriveFieldBuffer(1, view(calib.drivefield, :, :, :, startIdx:stopIdx), sequence)) - push!(deviceBuffer, TxDAQControllerBuffer(1, view(calib.applied, :, :, :, startIdx:stopIdx), protocol.txCont)) + contSeq = protocol.contSequence + dfBuffer = DriveFieldBuffer(1, view(calib.drivefield, :, :, :, startIdx:stopIdx), contSeq) + #controlBuffer = TxDAQControllerBuffer(1, view(calib.applied, :, :, :, startIdx:stopIdx), protocol.txCont) + if acqNumFrameAverages(protocol.params.sequence) > 1 + dfBuffer = AverageBuffer(dfBuffer, rxNumSamplesPerPeriod(sequence), 2, acqNumPeriodsPerFrame(sequence), acqNumFrameAverages(sequence)) + #controlBuffer = AverageBuffer(controlBuffer, rxNumSamplesPerPeriod(sequence), 2, acqNumPeriodsPerFrame(sequence), acqNumFrameAverages(sequence)) + end + push!(sinks, dfBuffer) + #push!(deviceBuffer, controlBuffer) end sequenceBuffer = AsyncBuffer(FrameSplitterBuffer(daq, sinks), daq) diff --git a/src/Protocols/Storage/ProducerConsumer.jl b/src/Protocols/Storage/ProducerConsumer.jl index 6660a31a..df129526 100644 --- a/src/Protocols/Storage/ProducerConsumer.jl +++ b/src/Protocols/Storage/ProducerConsumer.jl @@ -113,7 +113,7 @@ function asyncProducer(channel::Channel, protocol::Protocol, sequence::Sequence) end asyncConsumer(measState::SequenceMeasState) = asyncConsumer(measState.channel, measState.sequenceBuffer, measState.deviceBuffers) -function asyncConsumer(channel::Channel, sequenceBuffer::StorageBuffer, deviceBuffers::Union{Vector{DeviceBuffer}, Nothing} = nothing) +function asyncConsumer(channel::Channel, sequenceBuffer::StorageBuffer, deviceBuffers = nothing) @debug "Consumer start" while isopen(channel) || isready(channel) while isready(channel) From 292f07a2a9131be1a3ac819f3bf5550ddd526060 Mon Sep 17 00:00:00 2001 From: Jonas Schumacher Date: Wed, 25 Jun 2025 14:48:27 +0200 Subject: [PATCH 147/168] adapt code for different controller version * allow for different stepspermm per axis * allow for motor current version differences --- src/Devices/Robots/IselRobot.jl | 61 ++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/src/Devices/Robots/IselRobot.jl b/src/Devices/Robots/IselRobot.jl index 5ab8ad97..bc42ec58 100644 --- a/src/Devices/Robots/IselRobot.jl +++ b/src/Devices/Robots/IselRobot.jl @@ -40,7 +40,8 @@ Base.@kwdef struct IselRobotPortParams <: IselRobotParams minMaxVel::Vector{Int64} = [30,10000] # velocity in steps/s minMaxAcc::Vector{Int64} = [1,4000] # acceleration in (steps/s)/ms minMaxFreq::Vector{Int64} = [20,4000] # initial speed of acceleration ramp in steps/s - stepsPermm::Float64 = 100 + stepsPermm::Union{Vector{Int64}, Int64} = 100 + roundmmDigits::Int64 = 1 serial_port::String @add_serial_device_fields "\r" @@ -62,7 +63,8 @@ Base.@kwdef struct IselRobotPoolParams <: IselRobotParams minMaxVel::Vector{Int64} = [30,10000] # velocity in steps/s minMaxAcc::Vector{Int64} = [1,4000] # acceleration in (steps/s)/ms minMaxFreq::Vector{Int64} = [20,4000] # initial speed of acceleration ramp in steps/s - stepsPermm::Float64 = 100 + stepsPermm::Union{Vector{Int64}, Int64} = 100 + roundmmDigits::Int64 = 1 description::String @add_serial_device_fields "\r" @@ -109,7 +111,7 @@ function _getPosition(robot::IselRobot) ret = queryIsel(robot, "@0P", 19) checkIselError(string(ret[1])) pos = _parseIselPos(ret[2:19]) - return steps2mm.(pos, robot.params.stepsPermm) + return steps2mm(robot, pos) end function initSerialDevice(rob::IselRobot, params::IselRobotPortParams) @@ -149,22 +151,19 @@ function _enable(robot::IselRobot) robot.enableTime = time() _enable(robot, controllerVersion(robot)) end -function _enable(robot::IselRobot, version::IseliMCS8) - # NOP +function _enable(robot::IselRobot, version) + _setMotorCurrent(robot, version, true) end -function _enable(robot::IselRobot, version::IselC142) - _setMotorCurrent(robot, true) +function _enable(robot::IselRobot, version::IseliMCS8) + _setMotorCurrent(robot, version, true) end function _disable(robot::IselRobot) writeIOOutput(robot, zeros(Bool, 8)) _disable(robot, controllerVersion(robot)) end -function _disable(robot::IselRobot, version::IseliMCS8) - # NOP -end -function _disable(robot::IselRobot, version::IselC142) - _setMotorCurrent(robot, false) +function _disable(robot::IselRobot, version) + _setMotorCurrent(robot, version, false) end @@ -236,11 +235,11 @@ function _moveAbs(rob::IselRobot, pos::Vector{<:Unitful.Length}, speed::Union{Ve waitEnableTime(rob) # for z-axis two steps and velocities are needed, compare documentation # set second z steps to zero - steps = mm2steps.(pos, rob.params.stepsPermm) + steps = mm2steps(rob, pos) if speed === nothing speed = defaultVelocity(rob) end - vel = mm2steps.(speed, rob.params.stepsPermm) + vel = mm2steps(rob, speed) tempTimeout = rob.sd.timeout_ms # store the robot timeout default value # calclulate the timeout needed to do the full movement @@ -255,7 +254,7 @@ function _moveAbs(rob::IselRobot, pos::Vector{<:Unitful.Length}, speed::Union{Ve ret = queryIsel(rob, cmd) checkIselError(ret) else - error("Velocities set not in the range of $(steps2mm.(rob.params.minMaxVel, rob.params.stepsPermm)/u"s"), you are trying to set: $speed") + error("Velocities set not in the range of $(steps2mm(rob, rob.params.minMaxVel)/u"s"), you are trying to set: $speed") end rob.sd.timeout_ms = tempTimeout # set the robot Timeout to the default value end @@ -264,11 +263,11 @@ function _moveRel(rob::IselRobot, dist::Vector{<:Unitful.Length}, speed::Union{V waitEnableTime(rob) # for z-axis two steps and velocities are needed, compare documentation # set second z steps to zero - steps = mm2steps.(dist, rob.params.stepsPermm) + steps = mm2steps(rob, dist) if speed === nothing speed = defaultVelocity(rob) end - vel = mm2steps.(speed, rob.params.stepsPermm) + vel = mm2steps(rob, speed) tempTimeout = rob.sd.timeout_ms # store the robot timeout default value # calclulate the timeout needed to do the full movement @@ -281,7 +280,7 @@ function _moveRel(rob::IselRobot, dist::Vector{<:Unitful.Length}, speed::Union{V ret = queryIsel(rob, cmd) checkIselError(ret) else - error("Velocities set not in the range of $(steps2mm.(rob.params.minMaxVel, rob.params.stepsPermm)/u"s"), you are trying to set: $speed") + error("Velocities set not in the range of $(steps2mm(rob, rob.params.minMaxVel)/u"s"), you are trying to set: $speed") end rob.sd.timeout_ms = tempTimeout # set the robot Timeout to the default value end @@ -302,7 +301,12 @@ macro minimumISELversion(version::Int) end) end -function _setMotorCurrent(rob::IselRobot, power::Bool) +_setMotorCurrent(rob::IselRobot, power::Bool) = _setMotorCurrent(rob, controllerVersion(rob), power) +function _setMotorCurrent(rob::IselRobot, ::IseliMCS8, power::Bool) + ret = queryIsel(rob, string("@0B3,", Int(power))) + checkIselError(ret) +end +function _setMotorCurrent(rob::IselRobot, ::IselC142, power::Bool) ret = queryIsel(rob, string("@0B65529,", Int(!power))) checkIselError(ret) end @@ -315,22 +319,23 @@ function _parseIselPos(ret::AbstractString) return [xPos,yPos,zPos] end -function mm2steps(len::Unitful.Length, stepsPermm::Real) - temp = round(ustrip(u"mm", len), digits=1) # round to 100um due to step error after setting powerless - return round(Int64, temp * stepsPermm) +function mm2steps(rob::IselRobot, len::Vector{<:Unitful.Length}) + temp = round.(ustrip.(u"mm", len), digits=rob.params.roundmmDigits) # round to 100um due to step error after setting powerless + return round.(Int64, temp .* rob.params.stepsPermm) end -function mm2steps(len::Unitful.Velocity, stepsPermm::Real) - temp = round(ustrip(u"mm/s", len), digits=1) # round to 100um due to step error after setting powerless - return round(Int64, temp * stepsPermm) +function mm2steps(rob::IselRobot, len::Vector{<:Unitful.Velocity}) + temp = round.(ustrip.(u"mm/s", len), digits=rob.params.roundmmDigits) # round to 100um due to step error after setting powerless + return round.(Int64, temp .* rob.params.stepsPermm) end -steps2mm(steps::Integer, stepsPermm::Real) = Int64(steps) / stepsPermm * u"mm" +steps2mm(rob::IselRobot, steps::Vector{<:Integer}) = steps2mm(rob, Int64.(steps)) +steps2mm(rob::IselRobot, steps::Vector{Int64}) = steps ./ rob.params.stepsPermm * u"mm" """ Sets the Reference velocities of the axes x,y,z """ function setRefVelocity(rob::IselRobot, vel::Vector{<:Unitful.Velocity}) - vel = mm2steps.(vel, rob.params.stepsPermm) + vel = mm2steps(rob, vel) minVel = rob.params.minMaxVel[1] maxVel = rob.params.minMaxVel[2] @@ -339,7 +344,7 @@ function setRefVelocity(rob::IselRobot, vel::Vector{<:Unitful.Velocity}) cmd = string("@0d", vel[1], ",", vel[2], ",", vel[3], ",", vel[3]) else # TODO: check if this distinction is really necessary, both should be able to use @0d - cmd = string("@0Id", " ", vel[1], ",", vel[2], ",", vel[3], ",", vel[3]) + cmd = string("@0Id", vel[1], ",", vel[2], ",", vel[3], ",", vel[3]) end ret = queryIsel(rob, cmd) checkIselError(ret) From 5ccc8e6d5706300c7596a27ab5cf7f9c5998e610 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Mon, 30 Jun 2025 15:10:24 +0200 Subject: [PATCH 148/168] add option to wait for protocolhandler --- src/Utils/Console/ConsoleProtocolHandler.jl | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Utils/Console/ConsoleProtocolHandler.jl b/src/Utils/Console/ConsoleProtocolHandler.jl index 88cc2a02..17dcb7a8 100644 --- a/src/Utils/Console/ConsoleProtocolHandler.jl +++ b/src/Utils/Console/ConsoleProtocolHandler.jl @@ -71,7 +71,7 @@ function initProtocol(cph::ConsoleProtocolHandler) end export startProtocol -function startProtocol(cph::ConsoleProtocolHandler) +function startProtocol(cph::ConsoleProtocolHandler; block=false) try @info "Execute protocol with name `$(name(cph.protocol))`" @@ -101,6 +101,17 @@ function startProtocol(cph::ConsoleProtocolHandler) cph.protocolState = PS_INIT @debug "Start event handler" cph.eventHandler = Timer(timer -> eventHandler(cph, timer), 0.0, interval=0.05) + if block + while (cph.protocolState != PS_FINISHED || isopen(cph.eventHandler)) + #@info cph.protocolState isopen(cph.eventHandler) + if cph.protocolState==PS_FAILED + @error "Protocol failed!" + return false + end + sleep(0.5) + end + #@info cph.protocolState isopen(cph.eventHandler) + end return true end catch e @@ -157,7 +168,7 @@ function eventHandler(cph::ConsoleProtocolHandler, timer::Timer) end catch ex - @error "The eventhandler catched an exception." + @error "The eventhandler caught an exception." confirmFinishedProtocol(cph) close(timer) #showError(ex) From 530fe297347077cd0314ae496cece1849785545e Mon Sep 17 00:00:00 2001 From: jonschumacher Date: Thu, 3 Jul 2025 10:09:13 +0200 Subject: [PATCH 149/168] make txdaqcontroller less verbose --- src/Devices/Virtual/TxDAQController.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index 5cc73584..4f2ea2d9 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -419,7 +419,7 @@ function controlTx(txCont::TxDAQController, control::ControlSequence) channel = Channel{channelType(daq)}(32) buffer = AsyncBuffer(FrameSplitterBuffer(daq, StorageBuffer[DriveFieldBuffer(1, zeros(ComplexF64, controlMatrixShape(control)..., 1, acqNumFrames(control.currSequence)), control)]), daq) - @info "Control measurement started" + @debug "Control measurement started" producer = @async begin @debug "Starting control producer" endSample = asyncProducer(channel, daq, control.currSequence, isControlStep=true) @@ -436,9 +436,9 @@ function controlTx(txCont::TxDAQController, control::ControlSequence) end end wait(consumer) - @info "Control measurement finished" + @debug "Control measurement finished" - @info "Evaluating control step" + @debug "Evaluating control step" tmp = read(sink(buffer, DriveFieldBuffer)) @debug "Size of calc fields from ref" size(tmp) From f76ce9f94e219724a92fc5c245a70e5e0a8328c2 Mon Sep 17 00:00:00 2001 From: jonschumacher Date: Fri, 4 Jul 2025 10:02:25 +0200 Subject: [PATCH 150/168] adapt ContinousMeasurementProtocol to new TxController --- src/Protocols/ContinousMeasurementProtocol.jl | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Protocols/ContinousMeasurementProtocol.jl b/src/Protocols/ContinousMeasurementProtocol.jl index b63b4bf2..e4618a6c 100644 --- a/src/Protocols/ContinousMeasurementProtocol.jl +++ b/src/Protocols/ContinousMeasurementProtocol.jl @@ -62,12 +62,7 @@ function _init(protocol::ContinousMeasurementProtocol) protocol.cancelled = false protocol.finishAcknowledged = false if protocol.params.controlTx - controllers = getDevices(protocol.scanner, TxDAQController) - if length(controllers) > 1 - throw(IllegalStateException("Cannot unambiguously find a TxDAQController as the scanner has $(length(controllers)) of them")) - end - protocol.txCont = controllers[1] - protocol.txCont.currTx = nothing + protocol.txCont = getDevice(protocol.scanner, TxDAQController) else protocol.txCont = nothing end From 1de4e6fab0c83b1c23080c197d0671baca82a749 Mon Sep 17 00:00:00 2001 From: jonschumacher Date: Fri, 4 Jul 2025 10:03:15 +0200 Subject: [PATCH 151/168] avoid allocation on slicing --- src/Protocols/Storage/ChainableBuffer.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Protocols/Storage/ChainableBuffer.jl b/src/Protocols/Storage/ChainableBuffer.jl index 6b7ae1dc..4f9e34a8 100644 --- a/src/Protocols/Storage/ChainableBuffer.jl +++ b/src/Protocols/Storage/ChainableBuffer.jl @@ -26,7 +26,7 @@ function push!(avgBuffer::AverageBuffer{T}, frames::AbstractArray{T,4}) where {T # Insert into buffer toFrames = fr + fit toAvg = avgBuffer.setIndex + fit - avgBuffer.buffer[:, :, :, avgBuffer.setIndex:toAvg] = frames[:, :, :, fr:toFrames] + avgBuffer.buffer[:, :, :, avgBuffer.setIndex:toAvg] = @view frames[:, :, :, fr:toFrames] avgBuffer.setIndex += length(avgBuffer.setIndex:toAvg) fr = toFrames + 1 From cc3b726983538ea05c4ce102a5e66d49658fea5b Mon Sep 17 00:00:00 2001 From: "Labuser (CHIMPS)" Date: Tue, 29 Jul 2025 15:43:08 +0200 Subject: [PATCH 152/168] add compat for new RP version --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index e2529b8d..961f9117 100644 --- a/Project.toml +++ b/Project.toml @@ -49,7 +49,7 @@ Pkg = "1" Plots = "1.39" ProgressMeter = "1.5" REPL = "1" -RedPitayaDAQServer = "0.6, 0.7, 0.8, 0.9, 0.10" +RedPitayaDAQServer = "0.6, 0.7, 0.8, 0.9, 0.10, 0.11" RelocatableFolders = "1" ReplMaker = "0.2.7" Scratch = "1.1.0" From be7609e5f3c86dd7cb76b422d0121ad0d8e450b8 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Thu, 31 Jul 2025 10:04:25 +0200 Subject: [PATCH 153/168] improve error messages --- src/Devices/Virtual/SerialPortPool.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Devices/Virtual/SerialPortPool.jl b/src/Devices/Virtual/SerialPortPool.jl index 5b722ed9..54b734eb 100644 --- a/src/Devices/Virtual/SerialPortPool.jl +++ b/src/Devices/Virtual/SerialPortPool.jl @@ -76,6 +76,8 @@ function getSerialDevice(pool::SerialPortPool, description::String; kwargs...) @error e end end + else + error("No suitable Port found!") end return nothing end @@ -106,7 +108,7 @@ function initSerialDevice(device::Device, query::String, response::String) pool = dependency(device, SerialPortPool) sd = getSerialDevice(pool, query, response; serial_device_splatting(device.params)...) if isnothing(sd) - throw(ScannerConfigurationError("Device $(deviceID(device)) found no fitting serial port.")) + throw(ScannerConfigurationError("Device $(deviceID(device)) failed to connect to serial port.")) end return sd else @@ -120,7 +122,7 @@ function initSerialDevice(device::Device, description::String) pool = dependency(device, SerialPortPool) sd = getSerialDevice(pool, description; serial_device_splatting(device.params)...) if isnothing(sd) - throw(ScannerConfigurationError("Device $(deviceID(device)) found no fitting serial port with description $description.")) + throw(ScannerConfigurationError("Device $(deviceID(device)) failed to connect to serial port with description $description.")) end return sd else From 376136f00983134f6f1f91355c496f1f98778400 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Thu, 31 Jul 2025 10:21:28 +0200 Subject: [PATCH 154/168] add BG sequences and temperature check to MultiSequenceSMProtocol --- .../MultiSequenceSystemMatrixProtocol.jl | 93 ++++++++++++++++--- 1 file changed, 79 insertions(+), 14 deletions(-) diff --git a/src/Protocols/MultiSequenceSystemMatrixProtocol.jl b/src/Protocols/MultiSequenceSystemMatrixProtocol.jl index fc15926f..f7885edf 100644 --- a/src/Protocols/MultiSequenceSystemMatrixProtocol.jl +++ b/src/Protocols/MultiSequenceSystemMatrixProtocol.jl @@ -15,7 +15,14 @@ Base.@kwdef mutable struct MultiSequenceSystemMatrixProtocolParams <: ProtocolPa saveInCalibFolder::Bool = false "Seconds to wait between measurements" waitTime::Float64 = 0.0 + "Maximum temperature the coils can be at to start the next measurement" + coolDownTo::Float64 = 100 + "Sequence to use as a BG measurement" + bgSequence::Union{Sequence, Nothing} = nothing + "Number of BG measurements before and after the measurement" + numBGMeas::Int = 1 end + function MultiSequenceSystemMatrixProtocolParams(dict::Dict, scanner::MPIScanner) positions = nothing if haskey(dict, "Positions") @@ -25,15 +32,21 @@ function MultiSequenceSystemMatrixProtocolParams(dict::Dict, scanner::MPIScanner delete!(dict, "Positions") end - - sequence = nothing if haskey(dict, "sequences") sequences = Sequences(scanner, dict["sequences"]) pop!(dict, "sequences") end + + if haskey(dict, "bgSequence") + bgSequence = Sequence(scanner, dict["bgSequence"]) + pop!(dict, "bgSequence") + else + bgSequence = nothing + end params = params_from_dict(MultiSequenceSystemMatrixProtocolParams, dict) params.sequences = sequences params.positions = positions + params.bgSequence = bgSequence return params end MultiSequenceSystemMatrixProtocolParams(dict::Dict) = params_from_dict(MultiSequenceSystemMatrixProtocolParams, dict) @@ -42,6 +55,7 @@ Base.@kwdef mutable struct MultiSequenceSystemMatrixProtocol <: Protocol @add_protocol_fields MultiSequenceSystemMatrixProtocolParams systemMeasState::Union{SystemMatrixMeasState,Nothing} = nothing + allSequences::Union{Vector{Sequence}, Nothing} = nothing done::Bool = false cancelled::Bool = false @@ -76,13 +90,31 @@ function _init(protocol::MultiSequenceSystemMatrixProtocol) @warn "The MultiSequenceSystemMatrixProtocol has $(length(protocol.params.sequences)) sequences but you configured $(length(protocol.params.positions)) positions." end + if isnothing(getTemperatureSensor(protocol.scanner)) && protocol.params.coolDownTo < 100 + throw(ProtocolConfigurationError("The Protocol requires cooling down to $(protocol.params.coolDownTo) °C between measurements, but no temperature sensor is present in the scanner!)")) + end + protocol.systemMeasState = SystemMatrixMeasState() - numPos = length(protocol.params.sequences) - measIsBGPos = [false for i = 1:numPos] + numBGMeas = protocol.params.numBGMeas + if !isnothing(protocol.params.bgSequence) + protocol.allSequences = [repeat([protocol.params.bgSequence], numBGMeas); protocol.params.sequences; repeat([protocol.params.bgSequence], numBGMeas)] + else + protocol.allSequences = protocol.params.sequences + end + numPos = length(protocol.allSequences) + + if numPos == 0 + throw(ProtocolConfigurationError("Protocol has no sequences configured!")) + end + measIsBGPos = zeros(Bool, numPos) + if !isnothing(protocol.params.bgSequence) + measIsBGPos[1:numBGMeas] .= true + measIsBGPos[end-numBGMeas+1:end] .= true + end framesPerPos = zeros(Int64, numPos) posToIdx = zeros(Int64, numPos) - for (i, seq) in enumerate(protocol.params.sequences) + for (i, seq) in enumerate(protocol.allSequences) framesPerPos[i] = acqNumFrames(seq) end numTotalFrames = sum(framesPerPos) @@ -237,8 +269,25 @@ function performMeasurements(protocol::MultiSequenceSystemMatrixProtocol) break end + tempSensor = getTemperatureSensor(protocol.scanner) + if !isnothing(tempSensor) + for i=1:60 + T = getTemperature(tempSensor,1) + if T > protocol.params.coolDownTo + @info "Coils still cooling down: $T °C (goal: $(protocol.params.coolDownTo) °C)" + sleep(1) + else + @info "Coil temperature of $T °C is below goal of $(protocol.params.coolDownTo) °C. Starting next measurement..." + break + end + if i==60 + error("Timeout while waiting for coils to cool down!") + end + end + end + performMeasurement(protocol) - if protocol.systemMeasState.currPos > length(protocol.params.sequences) + if protocol.systemMeasState.currPos > length(protocol.allSequences) calib = protocol.systemMeasState daq = getDAQ(protocol.scanner) wait(calib.consumer) @@ -262,10 +311,17 @@ function performMeasurement(protocol::MultiSequenceSystemMatrixProtocol) # Prepare calib = protocol.systemMeasState index = calib.currPos - @info "Measurement $index of $(length(protocol.params.sequences))" + @info "Measurement $index of $(length(protocol.allSequences))" + if index == 1 || calib.measIsBGPos[index] != calib.measIsBGPos[index-1] + text = if calib.measIsBGPos[index]; "background" else "foreground" end + if askChoices(protocol, "Press continue when $text measurement can be taken", ["Cancel", "Continue"]) == 1 + throw(CancelException()) + end + end + daq = getDAQ(protocol.scanner) - sequence = protocol.params.sequences[index] + sequence = protocol.allSequences[index] if protocol.params.controlTx sequence = controlTx(protocol.txCont, sequence) end @@ -289,7 +345,7 @@ end # calib = protocol.systemMeasState # daq = getDAQ(protocol.scanner) -# sequence = protocol.params.sequences[calib.currPos] +# sequence = protocol.allSequences[calib.currPos] # if protocol.params.controlTx # if isnothing(protocol.contSequence) || protocol.restored || (calib.currPos == 1) # sequence = controlTx(protocol.txCont, sequence) @@ -313,7 +369,7 @@ function asyncConsumer(channel::Channel, protocol::MultiSequenceSystemMatrixProt calib = protocol.systemMeasState @info "readData" daq = getDAQ(protocol.scanner) - numFrames = acqNumFrames(protocol.params.sequences[index]) + numFrames = acqNumFrames(protocol.allSequences[index]) startIdx = calib.posToIdx[index] stopIdx = startIdx + numFrames - 1 @@ -358,6 +414,9 @@ function store(protocol::MultiSequenceSystemMatrixProtocol, index) params["measIsBGFrame"] = sysObj.measIsBGFrame #params["temperatures"] = vec(sysObj.temperatures) params["sequences"] = toDict.(protocol.params.sequences) + if !isnothing(protocol.params.bgSequence) + params["bgSequence"] = toDict(protocol.params.bgSequence) + end filename = file(protocol, "meta.toml") filename_backup = file(protocol, "meta.toml.backup") @@ -402,8 +461,14 @@ function restore(protocol::MultiSequenceSystemMatrixProtocol) if askChoices(protocol, message, ["Cancel", "Use"]) == 1 throw(CancelException()) end - seq = storedSeq - protocol.params.sequence + seq = storedSeq + protocol.params.sequence # What is going on here? + end + + if !isnothing(protocol.params.bgSequence) + protocol.allSequences = [protocol.params.bgSequence; protocol.params.sequences; protocol.params.bgSequence] + else + protocol.allSequences = protocol.params.sequences end # Drive Field @@ -417,7 +482,7 @@ function restore(protocol::MultiSequenceSystemMatrixProtocol) sysObj.signals = mmap(protocol, "signals.bin", Float32) - numTotalFrames = sum(acqNumFrames, seq) + numTotalFrames = sum(acqNumFrames, protocol.allSequences) numRxChannels = length(rxChannels(seq[1])) # kind of hacky, but actual rxChannels for RedPitaya are only set when setupRx is called rxNumSamplingPoints = rxNumSamplesPerPeriod(seq[1]) numPeriods = acqNumPeriodsPerFrame(seq[1]) @@ -458,7 +523,7 @@ function cancel(protocol::MultiSequenceSystemMatrixProtocol) end function handleEvent(protocol::MultiSequenceSystemMatrixProtocol, event::ProgressQueryEvent) - put!(protocol.biChannel, ProgressEvent(protocol.systemMeasState.currPos, length(protocol.params.sequences), "Position", event)) + put!(protocol.biChannel, ProgressEvent(protocol.systemMeasState.currPos, length(protocol.allSequences), "Position", event)) end function handleEvent(protocol::MultiSequenceSystemMatrixProtocol, event::DataQueryEvent) From ee6495fe82815b759d4a39e4b17dcd3b32bbaa97 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Thu, 31 Jul 2025 10:24:20 +0200 Subject: [PATCH 155/168] reinclude FOTemp --- src/Devices/Sensors/Temperature/FOTempLowLevel.jl | 8 ++++---- src/Devices/Sensors/Temperature/TemperatureSensor.jl | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Devices/Sensors/Temperature/FOTempLowLevel.jl b/src/Devices/Sensors/Temperature/FOTempLowLevel.jl index ea23d2ce..296b0bd4 100644 --- a/src/Devices/Sensors/Temperature/FOTempLowLevel.jl +++ b/src/Devices/Sensors/Temperature/FOTempLowLevel.jl @@ -65,8 +65,8 @@ Returns the averaged temperature of a channel. """ function getAveragedTemperature(ft::FOTemp,channel::Char) temp = query(ft.sd, "?01 $channel") - acq = readuntil(ft.sd.sp, '\n', ft.sd.timeout_ms) - return parse(Float64,split(temp," ")[2]) / 10 + #acq = readuntil(ft.sd.sp, '\n', ft.sd.timeout_ms) + return parse(Float64,split(temp," ")[2]) / 10 end """ @@ -74,8 +74,8 @@ Returns the averaged temperature of all channel. """ function getAveragedTemperature(ft::FOTemp) temp = query(ft.sd, "?02") - acq = readuntil(ft.sd.sp, '\n', ft.sd.timeout_ms) - return parse(Float64,split(temp," ")[2]) / 10 + #acq = readuntil(ft.sd.sp, '\n', ft.sd.timeout_ms) + return parse(Float64,split(temp," ")[2]) / 10 end """ diff --git a/src/Devices/Sensors/Temperature/TemperatureSensor.jl b/src/Devices/Sensors/Temperature/TemperatureSensor.jl index e77dce78..766b98ef 100644 --- a/src/Devices/Sensors/Temperature/TemperatureSensor.jl +++ b/src/Devices/Sensors/Temperature/TemperatureSensor.jl @@ -3,7 +3,7 @@ abstract type TemperatureSensor <: Device end include("ArduinoTemperatureSensor.jl") include("DummyTemperatureSensor.jl") -#include("FOTemp.jl") +include("FOTemp.jl") include("SimulatedTemperatureSensor.jl") Base.close(t::TemperatureSensor) = nothing From ee02b7d15b59540d61ab018ed7c48eaf9eaf9611 Mon Sep 17 00:00:00 2001 From: Jonas Schumacher Date: Thu, 31 Jul 2025 11:41:15 +0200 Subject: [PATCH 156/168] support temp boards that do not support setMaximumTemps --- src/Devices/Sensors/Temperature/ArduinoTemperatureSensor.jl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Devices/Sensors/Temperature/ArduinoTemperatureSensor.jl b/src/Devices/Sensors/Temperature/ArduinoTemperatureSensor.jl index db346565..cab27709 100644 --- a/src/Devices/Sensors/Temperature/ArduinoTemperatureSensor.jl +++ b/src/Devices/Sensors/Temperature/ArduinoTemperatureSensor.jl @@ -44,7 +44,11 @@ function _init(sensor::ArduinoTemperatureSensor) @info "Connection to ArduinoTempBox established." ard = SimpleArduino(;commandStart = params.commandStart, commandEnd = params.commandEnd, sd = sd) sensor.ard = ard - setMaximumTemps(sensor, params.maxTemps) + try + setMaximumTemps(sensor, params.maxTemps) + catch e + @warn "Temperature Sensor does not support setMaximumTemps!" error=e + end end function initSerialDevice(sensor::ArduinoTemperatureSensor, params::ArduinoTemperatureSensorPortParams) From 1899cd0be10163813b74f8ce30cfded4efc4f031 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Fri, 1 Aug 2025 11:06:33 +0200 Subject: [PATCH 157/168] set offsets to zero in clearTx --- src/Devices/DAQ/RedPitayaDAQ.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index c9047c45..39824078 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -1137,6 +1137,7 @@ function clearTx!(daq::RedPitayaDAQ) for comp = 1:4 @add_batch batch amplitudeDAC!(daq.rpc, channel, comp, 0.0) end + @add_batch batch offsetDAC!(daq.rpc, channel, 0.0) end end end From ff0d54550c825f9814c8f8be3eefb0124517cfde Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Fri, 1 Aug 2025 11:07:02 +0200 Subject: [PATCH 158/168] do not reset trigger if not active --- src/Devices/DAQ/RedPitayaDAQ.jl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index 39824078..f8fba908 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -1116,7 +1116,9 @@ function startTx(daq::RedPitayaDAQ; isControlStep=false) end function stopTx(daq::RedPitayaDAQ) - masterTrigger!(daq.rpc, false) + if masterTrigger(daq.rpc) + masterTrigger!(daq.rpc, false) # only deactivate trigger if it is currently active + end execute!(daq.rpc) do batch @add_batch batch serverMode!(daq.rpc, CONFIGURATION) for channel in 1:2*length(daq.rpc) From b12fc87ec314c89694a3e2216adda0e0cbcd26de Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Thu, 14 Aug 2025 17:43:33 +0200 Subject: [PATCH 159/168] improve error message --- src/Devices/Virtual/SerialPortPool.jl | 2 +- src/Protocols/MPIMeasurementProtocol.jl | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Devices/Virtual/SerialPortPool.jl b/src/Devices/Virtual/SerialPortPool.jl index 54b734eb..8e269f45 100644 --- a/src/Devices/Virtual/SerialPortPool.jl +++ b/src/Devices/Virtual/SerialPortPool.jl @@ -77,7 +77,7 @@ function getSerialDevice(pool::SerialPortPool, description::String; kwargs...) end end else - error("No suitable Port found!") + throw(ScannerConfigurationError("No suitable SerialPort for `$description` found in SerialPortPool!")) end return nothing end diff --git a/src/Protocols/MPIMeasurementProtocol.jl b/src/Protocols/MPIMeasurementProtocol.jl index 220c8766..435c6370 100644 --- a/src/Protocols/MPIMeasurementProtocol.jl +++ b/src/Protocols/MPIMeasurementProtocol.jl @@ -83,7 +83,6 @@ function timeEstimate(protocol::MPIMeasurementProtocol) totalTime = (samplesPerFrame * totalFrames) / (125e6/(txBaseFrequency(seq)/rxSamplingRate(seq))) time = totalTime * 1u"s" est = string(time) - @show est end return est end From d5e3e48cd2a8b8b963d21cab0aeb9e5157aa00d2 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Thu, 14 Aug 2025 17:43:48 +0200 Subject: [PATCH 160/168] fix merge error --- src/Scanner.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Scanner.jl b/src/Scanner.jl index 5104220e..bcbe3c92 100644 --- a/src/Scanner.jl +++ b/src/Scanner.jl @@ -239,12 +239,12 @@ mutable struct MPIScanner configDir = findConfigDir(name) params = getScannerParams(configDir) - #@info "Instantiating scanner `$name` from configuration file at `$filename`." + @info "Instantiating scanner `$name` from configuration file at `$(joinpath(configDir, "Scanner.toml"))`." generalParams = params_from_dict(MPIScannerGeneral, params["General"]) @assert generalParams.name == name "The folder name and the scanner name in the configuration do not match." devices = initiateDevices(configDir, params["Devices"], robust = robust) - scanner = new(name, filename, generalParams, devices) + scanner = new(name, joinpath(configDir, "Scanner.toml"), generalParams, devices) return scanner end From db818d2864e729ddc2f19b28adddfbff02777e58 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Thu, 14 Aug 2025 18:20:28 +0200 Subject: [PATCH 161/168] fix tests --- src/Devices/Sensors/Temperature/FOTemp.jl | 2 +- src/Scanner.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Devices/Sensors/Temperature/FOTemp.jl b/src/Devices/Sensors/Temperature/FOTemp.jl index b7b5333c..84c88f9e 100644 --- a/src/Devices/Sensors/Temperature/FOTemp.jl +++ b/src/Devices/Sensors/Temperature/FOTemp.jl @@ -1,3 +1,3 @@ -export FOTemp, gettemperature, gettemperatures, logtemperature, logtemperatures +export FOTemp include("FOTempLowLevel.jl") diff --git a/src/Scanner.jl b/src/Scanner.jl index bcbe3c92..8ef2961a 100644 --- a/src/Scanner.jl +++ b/src/Scanner.jl @@ -411,7 +411,7 @@ init(scanner::MPIScanner, devices::Vector{<:Device}; kwargs...) = init(scanner, This does not initialize devices that themselves depend on the given devices. """ function init(scanner::MPIScanner, deviceIDs::Vector{String} = getDeviceIDs(scanner); kwargs...) - configDir = scanner.configDir + configDir = configDir(scanner) params = getScannerParams(configDir)["Devices"] devices = dependenciesDFS(deviceIDs, params) From ca784a971ab0726abb95b84a6bab8ded8eff2f6b Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Fri, 15 Aug 2025 09:54:06 +0200 Subject: [PATCH 162/168] fix tests again --- src/Scanner.jl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Scanner.jl b/src/Scanner.jl index 8ef2961a..1d26d534 100644 --- a/src/Scanner.jl +++ b/src/Scanner.jl @@ -411,15 +411,14 @@ init(scanner::MPIScanner, devices::Vector{<:Device}; kwargs...) = init(scanner, This does not initialize devices that themselves depend on the given devices. """ function init(scanner::MPIScanner, deviceIDs::Vector{String} = getDeviceIDs(scanner); kwargs...) - configDir = configDir(scanner) - params = getScannerParams(configDir)["Devices"] + params = getScannerParams(configDir(scanner))["Devices"] devices = dependenciesDFS(deviceIDs, params) order = filter(x -> in(x, devices), params["initializationOrder"]) getDevices(close, scanner, order) - deviceDict = initiateDevices(configDir, params; kwargs..., order = order) + deviceDict = initiateDevices(configDir(scanner), params; kwargs..., order = order) for (id, device) in deviceDict scanner.devices[id] = device From 97f01c96751214695131e3945d00554da29f517d Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Fri, 15 Aug 2025 11:36:20 +0200 Subject: [PATCH 163/168] dont test on incompatible julia version --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ada4a1f3..16ffdd7b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: fail-fast: false matrix: version: - - '1.9' + - 'lts' - '1' #- 'nightly' os: From d3a04b125cb3861667d95a875d197b5caaa74ecf Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Wed, 3 Sep 2025 14:37:37 +0200 Subject: [PATCH 164/168] fix caching of DC results --- src/Devices/Virtual/TxDAQController.jl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index 4f2ea2d9..67f05440 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -574,7 +574,11 @@ function updateCachedCalibration(txCont::TxDAQController, cont::AWControlSequenc @debug "Cached calibration result:" chId f finalCalibration[res] end - txCont.lastDCResults = cont.dcSearch[end-1:end] + if length(cont.dcSearch) >= 2 + txCont.lastDCResults = cont.dcSearch[end-1:end] + else + txCont.lastDCResults = nothing + end txCont.lastChannelIDs = channelIDs @debug "Cached DC result" txCont.lastDCResults From 26c3c6503388382412fc3ddb15cd4c7c9b3175c2 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Thu, 4 Sep 2025 15:04:02 +0200 Subject: [PATCH 165/168] first version of changelog --- CHANGELOG.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..f766f702 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,44 @@ +# Changelog + +## 0.6.0 + +### Most Important Breaking Changes +- **Breaking**: There have been multiple changes to arguments in the Scanner.toml related to both devices and the scanner, please find a detailed migration guide [](here) +- **Breaking**: the receive transfer function is now defined per receive channel instead of per scanner, this allows the sequence to flexibly select receive channels and assemble the correct TF + +### Improved support for arbitrary waveform components +- define an `ArbitraryElectricalComponent` by using an amplitude, phase and base waveform (`values`) +- `values` can either be a vector or the filename to an .h5 file with field "/values" located in the new `Waveforms` folder of the Scanner +- added TX controller for arbitrary waveform components (see next section) + +### Updated TxDAQController and Feedback +- completely reworked internals of `TxDAQController` device +- added new `ControlSequence` type structure to implement a tx controller to control arbitrary waveform and DC enabled channels, the type of ControlSequence to be used is automatically detected based on the requirements of the sequence that should be controlled, the old behaviour is implemented as `CrossCouplingControlSequence` +- split `amplitudeAccuracy` and `fieldToVolDeviation` settings into relative and absolute values to improve flexibility, the two conditions are combined as OR +- `phaseAccuracy` has a unit now +- added caching of last control values to increase control speed of repeating measurements +- removed `correctCrossCoupling` setting from TxDAQControllerParams, if any field sets decouple=true the controller will try to decouple it + + +### New MPS Measurement Protocol +Updated the `MPSMeasurementProtocol` used to measure hybrid system matrix measurements in an MPS. +New features include: +- offsets are now defined as a `ProtocolOffsetElectricalChannel` directly in the sequence instead of the protocol +- added a wait time per offset channel, to account for slow DC sources. Any data recorded during this settling time will be discarded +- added functionality to RedPitayaDAQ channels to use H-bridges for switching the polarity of DC offsets +- new algorithm ordering the channels to reduce total wait time +- save data in proper system matrix format for hybrid reconstruction + +### New MultiSequenceSMProtocol +Measures a (hybrid) system matrix that is defined by one `Sequence` per position, this can be used to flexibly vary any component of the field sequence like amplitudes, phases and offsets. The individual measurements will be saved together as frames in a joint MDF file. Between the individual measurements the system can be instructed to wait until the value of a temperature sensor is below a configurable threshhold. + +### General Updates +- the phase of signal components can now also be one of {"cosine", "cos", "sine", "sin", "-cosine", "-cos", "-sine", "-sin"} instead of giving the phase directly +- a magnetic field that needs to be decoupled will also require control +- frequency dividers can now be rational, the trajectory length will still be an integer using the lcm of the numerators +- all field amplitudes can now be given as a voltage, circumventing field control +- add `block` keyword argument to `startProtocol` of the ConsoleProtocolHandler to only return from the function when the protocol is finished +- devices now have a config file parameter containing the file from which they were initialized +- re-included implementation for fiber optical temperature sensor (FOTemp.jl) +- small fixes regarding different Isel robot versions +- renamed the `saveAsSystemMatrix` parameter to `saveInCalibFolder` \ No newline at end of file From b0c81020bb5f3a3214c63b416b9953667f26593c Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Thu, 4 Sep 2025 16:55:05 +0200 Subject: [PATCH 166/168] remove unused defaultSequence parameter from scanner --- config/SimpleRedPitayaScanner/Scanner.toml | 1 - config/SimpleSimulatedScanner/Scanner.toml | 1 - docs/src/lib/framework/scanner.md | 1 - src/Scanner.jl | 5 ----- .../TestBrokenDeviceMissingConfigFile/Scanner.toml | 1 - .../TestBrokenDeviceMissingDependencies/Scanner.toml | 1 - test/TestConfigs/TestBrokenDeviceMissingID/Scanner.toml | 1 - .../TestConfigs/TestBrokenDeviceMissingOptional/Scanner.toml | 1 - test/TestConfigs/TestBrokenDeviceMissingParams/Scanner.toml | 1 - test/TestConfigs/TestBrokenDeviceMissingPresent/Scanner.toml | 1 - test/TestConfigs/TestDeviceMissingDependencies/Scanner.toml | 1 - test/TestConfigs/TestDeviceUninitDependencies/Scanner.toml | 1 - test/TestConfigs/TestDeviceWorkingScanner/Scanner.toml | 1 - test/TestConfigs/TestDeviceWrongDependencies/Scanner.toml | 1 - test/TestConfigs/TestFlexibleScanner/Scanner.toml | 1 - test/TestConfigs/TestRedPitayaScanner/Scanner.toml | 1 - test/TestConfigs/TestSimpleSimulatedScanner/Scanner.toml | 1 - 17 files changed, 21 deletions(-) diff --git a/config/SimpleRedPitayaScanner/Scanner.toml b/config/SimpleRedPitayaScanner/Scanner.toml index 19464ec9..71491fd4 100644 --- a/config/SimpleRedPitayaScanner/Scanner.toml +++ b/config/SimpleRedPitayaScanner/Scanner.toml @@ -7,7 +7,6 @@ topology = "FFL" gradient = "42T/m" #datasetStore = "/opt/data/HeadScanner" datasetStore = "~/.mpi/Data" -defaultSequence = "1DSequence" defaultProtocol = "MPIMeasurement" producerThreadID = 2 consumerThreadID = 3 diff --git a/config/SimpleSimulatedScanner/Scanner.toml b/config/SimpleSimulatedScanner/Scanner.toml index b3d7505c..3d683896 100644 --- a/config/SimpleSimulatedScanner/Scanner.toml +++ b/config/SimpleSimulatedScanner/Scanner.toml @@ -6,7 +6,6 @@ name = "SimpleSimulatedScanner" topology = "FFL" # Of course ;) gradient = "42T/m" # Note: This is later parsed with Unitful datasetStore = "/home/foerger/Documents/DatasetStores/store1" -defaultSequence = "1DSequence" defaultProtocol = "MPIMeasurement" producerThreadID = 2 consumerThreadID = 3 diff --git a/docs/src/lib/framework/scanner.md b/docs/src/lib/framework/scanner.md index 01f8135c..29fa52f1 100644 --- a/docs/src/lib/framework/scanner.md +++ b/docs/src/lib/framework/scanner.md @@ -11,7 +11,6 @@ MPIMeasurements.scannerName MPIMeasurements.scannerTopology MPIMeasurements.scannerGradient MPIMeasurements.scannerDatasetStore -MPIMeasurements.defaultSequence MPIMeasurements.defaultProtocol ``` diff --git a/src/Scanner.jl b/src/Scanner.jl index 1d26d534..01463675 100644 --- a/src/Scanner.jl +++ b/src/Scanner.jl @@ -199,8 +199,6 @@ Base.@kwdef struct MPIScannerGeneral gradient::Union{typeof(1u"T/m"), Nothing} = nothing "Path of the dataset store." datasetStore::String = "" - "Default sequence of the scanner." - defaultSequence::String = "" "Default protocol of the scanner." defaultProtocol::String = "" "Thread ID of the producer thread." @@ -448,9 +446,6 @@ scannerGradient(scanner::MPIScanner) = scanner.generalParams.gradient "Path of the dataset store." scannerDatasetStore(scanner::MPIScanner) = scanner.generalParams.datasetStore -"Default sequence of the scanner." -defaultSequence(scanner::MPIScanner) = scanner.generalParams.defaultSequence - "Default protocol of the scanner." defaultProtocol(scanner::MPIScanner) = scanner.generalParams.defaultProtocol diff --git a/test/TestConfigs/TestBrokenDeviceMissingConfigFile/Scanner.toml b/test/TestConfigs/TestBrokenDeviceMissingConfigFile/Scanner.toml index 97a3578f..d4a2a3f1 100644 --- a/test/TestConfigs/TestBrokenDeviceMissingConfigFile/Scanner.toml +++ b/test/TestConfigs/TestBrokenDeviceMissingConfigFile/Scanner.toml @@ -6,7 +6,6 @@ name = "TestBrokenDeviceMissingConfigFile" topology = "FFL" gradient = "42T/m" datasetStore = "./tmp/TestSimpleSimulatedScannerStore" -defaultSequence = "SimpleSimulatedSequence" [Devices] initializationOrder = [ diff --git a/test/TestConfigs/TestBrokenDeviceMissingDependencies/Scanner.toml b/test/TestConfigs/TestBrokenDeviceMissingDependencies/Scanner.toml index 43085bea..baf24a3d 100644 --- a/test/TestConfigs/TestBrokenDeviceMissingDependencies/Scanner.toml +++ b/test/TestConfigs/TestBrokenDeviceMissingDependencies/Scanner.toml @@ -6,7 +6,6 @@ name = "TestBrokenDeviceMissingDependencies" topology = "FFL" gradient = "42T/m" datasetStore = "./tmp/TestSimpleSimulatedScannerStore" -defaultSequence = "SimpleSimulatedSequence" [Devices] initializationOrder = [ diff --git a/test/TestConfigs/TestBrokenDeviceMissingID/Scanner.toml b/test/TestConfigs/TestBrokenDeviceMissingID/Scanner.toml index bf1d68ec..88096b60 100644 --- a/test/TestConfigs/TestBrokenDeviceMissingID/Scanner.toml +++ b/test/TestConfigs/TestBrokenDeviceMissingID/Scanner.toml @@ -6,7 +6,6 @@ name = "TestBrokenDeviceMissingID" topology = "FFL" gradient = "42T/m" datasetStore = "./tmp/TestSimpleSimulatedScannerStore" -defaultSequence = "SimpleSimulatedSequence" [Devices] initializationOrder = [ diff --git a/test/TestConfigs/TestBrokenDeviceMissingOptional/Scanner.toml b/test/TestConfigs/TestBrokenDeviceMissingOptional/Scanner.toml index db827780..aa209378 100644 --- a/test/TestConfigs/TestBrokenDeviceMissingOptional/Scanner.toml +++ b/test/TestConfigs/TestBrokenDeviceMissingOptional/Scanner.toml @@ -6,7 +6,6 @@ name = "TestBrokenDeviceMissingOptional" topology = "FFL" gradient = "42T/m" datasetStore = "./tmp/TestSimpleSimulatedScannerStore" -defaultSequence = "SimpleSimulatedSequence" [Devices] initializationOrder = [ diff --git a/test/TestConfigs/TestBrokenDeviceMissingParams/Scanner.toml b/test/TestConfigs/TestBrokenDeviceMissingParams/Scanner.toml index 5c802273..83e0bd1e 100644 --- a/test/TestConfigs/TestBrokenDeviceMissingParams/Scanner.toml +++ b/test/TestConfigs/TestBrokenDeviceMissingParams/Scanner.toml @@ -6,7 +6,6 @@ name = "TestBrokenDeviceMissingParams" topology = "FFL" gradient = "42T/m" datasetStore = "./tmp/TestSimpleSimulatedScannerStore" -defaultSequence = "SimpleSimulatedSequence" [Devices] initializationOrder = [ diff --git a/test/TestConfigs/TestBrokenDeviceMissingPresent/Scanner.toml b/test/TestConfigs/TestBrokenDeviceMissingPresent/Scanner.toml index ed117d40..da19da1b 100644 --- a/test/TestConfigs/TestBrokenDeviceMissingPresent/Scanner.toml +++ b/test/TestConfigs/TestBrokenDeviceMissingPresent/Scanner.toml @@ -6,7 +6,6 @@ name = "TestBrokenDeviceMissingPresent" topology = "FFL" gradient = "42T/m" datasetStore = "./tmp/TestSimpleSimulatedScannerStore" -defaultSequence = "SimpleSimulatedSequence" [Devices] initializationOrder = [ diff --git a/test/TestConfigs/TestDeviceMissingDependencies/Scanner.toml b/test/TestConfigs/TestDeviceMissingDependencies/Scanner.toml index c22e9fa7..e9e8a499 100644 --- a/test/TestConfigs/TestDeviceMissingDependencies/Scanner.toml +++ b/test/TestConfigs/TestDeviceMissingDependencies/Scanner.toml @@ -6,7 +6,6 @@ name = "TestDeviceMissingDependencies" topology = "FFL" gradient = "42T/m" datasetStore = "./tmp/TestSimpleSimulatedScannerStore" -defaultSequence = "SimpleSimulatedSequence" [Devices] initializationOrder = [ diff --git a/test/TestConfigs/TestDeviceUninitDependencies/Scanner.toml b/test/TestConfigs/TestDeviceUninitDependencies/Scanner.toml index 3a4db612..b029f9bc 100644 --- a/test/TestConfigs/TestDeviceUninitDependencies/Scanner.toml +++ b/test/TestConfigs/TestDeviceUninitDependencies/Scanner.toml @@ -6,7 +6,6 @@ name = "TestDeviceUninitDependencies" topology = "FFL" gradient = "42T/m" datasetStore = "./tmp/TestSimpleSimulatedScannerStore" -defaultSequence = "SimpleSimulatedSequence" [Devices] initializationOrder = [ diff --git a/test/TestConfigs/TestDeviceWorkingScanner/Scanner.toml b/test/TestConfigs/TestDeviceWorkingScanner/Scanner.toml index f6866ac9..29ff545b 100644 --- a/test/TestConfigs/TestDeviceWorkingScanner/Scanner.toml +++ b/test/TestConfigs/TestDeviceWorkingScanner/Scanner.toml @@ -6,7 +6,6 @@ name = "TestDeviceWorkingScanner" topology = "FFL" gradient = "42T/m" datasetStore = "./tmp/TestSimpleSimulatedScannerStore" -defaultSequence = "SimpleSimulatedSequence" [Devices] initializationOrder = [ diff --git a/test/TestConfigs/TestDeviceWrongDependencies/Scanner.toml b/test/TestConfigs/TestDeviceWrongDependencies/Scanner.toml index d2074249..eb1b4ecf 100644 --- a/test/TestConfigs/TestDeviceWrongDependencies/Scanner.toml +++ b/test/TestConfigs/TestDeviceWrongDependencies/Scanner.toml @@ -6,7 +6,6 @@ name = "TestDeviceWrongDependencies" topology = "FFL" gradient = "42T/m" datasetStore = "./tmp/TestSimpleSimulatedScannerStore" -defaultSequence = "SimpleSimulatedSequence" [Devices] initializationOrder = [ diff --git a/test/TestConfigs/TestFlexibleScanner/Scanner.toml b/test/TestConfigs/TestFlexibleScanner/Scanner.toml index 3f375828..f8c59283 100644 --- a/test/TestConfigs/TestFlexibleScanner/Scanner.toml +++ b/test/TestConfigs/TestFlexibleScanner/Scanner.toml @@ -6,7 +6,6 @@ name = "TestFlexibleScanner" topology = "FFL" gradient = "42T/m" datasetStore = "./tmp/TestFlexibleScannerStore" -defaultSequence = "" [Devices] initializationOrder = [ diff --git a/test/TestConfigs/TestRedPitayaScanner/Scanner.toml b/test/TestConfigs/TestRedPitayaScanner/Scanner.toml index 66b02096..0127c259 100644 --- a/test/TestConfigs/TestRedPitayaScanner/Scanner.toml +++ b/test/TestConfigs/TestRedPitayaScanner/Scanner.toml @@ -6,7 +6,6 @@ name = "TestRedPitayaScanner" topology = "FFL" gradient = "42T/m" datasetStore = "./tmp/TestRedPitayaScannerStore" -defaultSequence = "" [Devices] initializationOrder = [ diff --git a/test/TestConfigs/TestSimpleSimulatedScanner/Scanner.toml b/test/TestConfigs/TestSimpleSimulatedScanner/Scanner.toml index 8cb74a3e..699147ee 100644 --- a/test/TestConfigs/TestSimpleSimulatedScanner/Scanner.toml +++ b/test/TestConfigs/TestSimpleSimulatedScanner/Scanner.toml @@ -6,7 +6,6 @@ name = "TestSimpleSimulatedScanner" topology = "FFL" gradient = "42T/m" datasetStore = "./tmp/TestSimpleSimulatedScannerStore" -defaultSequence = "SimpleSimulatedSequence" [Devices] initializationOrder = [ From aaa9d372fa3e4d3f3cb53e3627fdc6133b564d08 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Fri, 5 Sep 2025 09:25:50 +0200 Subject: [PATCH 167/168] update documentation --- CHANGELOG.md | 5 ++- docs/make.jl | 31 +++++++++------- docs/src/config/devices.md | 13 +++++++ docs/src/config/protocols.md | 6 +++ docs/src/config/scanner.md | 4 ++ docs/src/config/sequence.md | 17 +++++++++ docs/src/config/upgrade.md | 37 +++++++++++++++++++ docs/src/framework/devices.md | 2 +- docs/src/framework/scanner.md | 17 ++++----- docs/src/framework/sequences.md | 1 + docs/src/lib/base/protocols/mpimeasurement.md | 5 --- src/Devices/DAQ/DAQ.jl | 12 ++++++ src/Devices/DAQ/RedPitayaDAQ.jl | 10 +++++ .../SimpleBoreCollisionModule.jl | 5 +++ src/Devices/Robots/IgusRobot.jl | 5 +++ src/Devices/Robots/IselRobot.jl | 10 +++++ src/Devices/Virtual/TxDAQController.jl | 16 +++++++- src/Protocols/ContinousMeasurementProtocol.jl | 4 +- src/Protocols/MPIForceProtocol.jl | 4 +- src/Protocols/MPIMeasurementProtocol.jl | 4 +- src/Protocols/MPSMeasurementProtocol.jl | 11 +++--- .../MechanicalMPIMeasurementProtocol.jl | 4 +- .../MultiSequenceSystemMatrixProtocol.jl | 4 +- .../RobotBasedMagneticFieldStaticProtocol.jl | 5 +++ .../RobotBasedSystemMatrixProtocol.jl | 4 +- .../RobotBasedTDesignFieldProtocol.jl | 5 +++ src/Protocols/RobotMPIMeasurementProtocol.jl | 4 +- src/Scanner.jl | 4 ++ src/Sequences/MagneticField.jl | 2 + src/Sequences/PeriodicElectricalChannel.jl | 27 +++++++++++--- .../ProtocolOffsetElectricalChannel.jl | 5 +++ src/Sequences/StepwiseElectricalChannel.jl | 6 ++- 32 files changed, 239 insertions(+), 50 deletions(-) create mode 100644 docs/src/config/devices.md create mode 100644 docs/src/config/protocols.md create mode 100644 docs/src/config/scanner.md create mode 100644 docs/src/config/sequence.md create mode 100644 docs/src/config/upgrade.md delete mode 100644 docs/src/lib/base/protocols/mpimeasurement.md diff --git a/CHANGELOG.md b/CHANGELOG.md index f766f702..ec0dfb30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ - split `amplitudeAccuracy` and `fieldToVolDeviation` settings into relative and absolute values to improve flexibility, the two conditions are combined as OR - `phaseAccuracy` has a unit now - added caching of last control values to increase control speed of repeating measurements +- feedback calibration is now handled as a complex valued, optionally frequency dependent transfer function +- forward calibration of tx channels can now be a complex number to include a phase shift - removed `correctCrossCoupling` setting from TxDAQControllerParams, if any field sets decouple=true the controller will try to decouple it @@ -41,4 +43,5 @@ Measures a (hybrid) system matrix that is defined by one `Sequence` per position - devices now have a config file parameter containing the file from which they were initialized - re-included implementation for fiber optical temperature sensor (FOTemp.jl) - small fixes regarding different Isel robot versions -- renamed the `saveAsSystemMatrix` parameter to `saveInCalibFolder` \ No newline at end of file +- renamed the `saveAsSystemMatrix` parameter to `saveInCalibFolder` +- removed `defaultSequence` parameter from scanner as the sequence is always defined in the protocol \ No newline at end of file diff --git a/docs/make.jl b/docs/make.jl index 4525aafe..add0db6e 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -7,18 +7,25 @@ using Unitful makedocs( sitename = "MPIMeasurements", authors = "Tobias Knopp et al.", - format = Documenter.HTML(prettyurls = false), + format = Documenter.HTML(prettyurls = false, size_threshold = 500000,), modules = [MPIMeasurements], pages = [ "Home" => "index.md", "Installation" => "installation.md", - "Framework" => Any[ + "Framework Explanations" => Any[ "Scanner" => "framework/scanner.md", "Devices" => "framework/devices.md", "Sequences" => "framework/sequences.md", "Protocols" => "framework/protocols.md", "Examples" => "framework/examples.md", ], + "Configuration Files / Parameters" => Any[ + "Upgrade Guide" => "config/upgrade.md", + "Scanner" => "config/scanner.md", + "Devices" => "config/devices.md", + "Sequences" => "config/sequence.md", + "Protocols" => "config/protocols.md", + ], "Library" => Any[ "Framework" => Any[ "Scanner" => "lib/framework/scanner.md", @@ -26,20 +33,16 @@ makedocs( "Sequence" => "lib/framework/sequence.md", "Protocol" => "lib/framework/protocol.md", ], - "Base" => Any[ - "Devices" => Any[ - "Robots" => Any[ - "Interface" => "lib/base/devices/robots/interface.md", - "Isel" => "lib/base/devices/robots/isel.md" - ], - "Virtual" => Any[ - "Serial Port Pool" => "lib/base/devices/virtual/serialportpool.md", - ] + + "Devices" => Any[ + "Robots" => Any[ + "Interface" => "lib/base/devices/robots/interface.md", + "Isel" => "lib/base/devices/robots/isel.md" ], - "Protocols" => Any[ - "MPIMeasurement" => "lib/base/protocols/mpimeasurement.md" + "Virtual" => Any[ + "Serial Port Pool" => "lib/base/devices/virtual/serialportpool.md", ] - ] + ], ], ], warnonly = [:docs_block, :autodocs_block, :cross_references], diff --git a/docs/src/config/devices.md b/docs/src/config/devices.md new file mode 100644 index 00000000..a4f4d0c2 --- /dev/null +++ b/docs/src/config/devices.md @@ -0,0 +1,13 @@ +# Device Parameters +## DAQ +```@docs +MPIMeasurements.RedPitayaDAQParams +MPIMeasurements.RedPitayaLUTChannelParams +MPIMeasurements.DAQRxChannelParams +MPIMeasurements.DAQTxChannelParams +``` + +## TxController +```@docs +MPIMeasurements.TxDAQControllerParams +``` \ No newline at end of file diff --git a/docs/src/config/protocols.md b/docs/src/config/protocols.md new file mode 100644 index 00000000..e4b990cd --- /dev/null +++ b/docs/src/config/protocols.md @@ -0,0 +1,6 @@ +# Protocols + +```@autodocs +Modules = [MPIMeasurements] +Filter = t -> typeof(t) === DataType && t <: MPIMeasurements.ProtocolParams +``` \ No newline at end of file diff --git a/docs/src/config/scanner.md b/docs/src/config/scanner.md new file mode 100644 index 00000000..f066b832 --- /dev/null +++ b/docs/src/config/scanner.md @@ -0,0 +1,4 @@ +# Scanner Parameters +```@docs +MPIMeasurements.MPIScannerGeneral +``` \ No newline at end of file diff --git a/docs/src/config/sequence.md b/docs/src/config/sequence.md new file mode 100644 index 00000000..47347807 --- /dev/null +++ b/docs/src/config/sequence.md @@ -0,0 +1,17 @@ +# Sequence Parameters +## Magnetic Field +```@docs +MagneticField +``` + +## Channels +```@autodocs +Modules = [MPIMeasurements] +Filter = t -> typeof(t) === DataType && t <: MPIMeasurements.TxChannel +``` + +## Components +```@autodocs +Modules = [MPIMeasurements] +Filter = t -> typeof(t) === DataType && t <: MPIMeasurements.ElectricalComponent +``` \ No newline at end of file diff --git a/docs/src/config/upgrade.md b/docs/src/config/upgrade.md new file mode 100644 index 00000000..ac784e48 --- /dev/null +++ b/docs/src/config/upgrade.md @@ -0,0 +1,37 @@ +# Upgrade Guide +This page should give you hints on what to change in your configuration files when upgrading between breaking releases of this package. This guide focusses on removed or changed parameters not on new features that have a sensible default value, for that please check the release notes. + +## v0.5 to v0.6 +### Scanner and Devices +- the `transferFunction` parameter is no longer in the [General] section, but is now a per-channel parameter attached to each `DAQRxChannelParams`. Use `transferFunction = "tfFile.h5:2"` to select channel 2 out of the file containing multilpe channels +- the `feedback` group is removed from the tx channels and replaced by two other parameters: `feedback.channelID` is replaced by `feedbackChannelID` in the tx channel directly and `feedback.calibration` is replaced by the `transferFunction` parameter in the corresponding receive channel. The `transferFunction` parameter can correctly parse a single (complex) value with units + +### TxDAQController + +**Old parameter:** \ +`amplitudeAccuracy` \ +**Replaced by:** \ +`absoluteAmplitudeAccuracy`, absolute control accuracy threshhold with units of magnetic field, e.g. "50µT" \ +`relativeAmplitudeAcccuracy`, defined as the allowable deviation of the amplitude as a ratio of the desired amplitude, e.g. 0.001 + +**Old parameter:** \ +`fieldToVoltDeviation` \ +**Replaced by:** \ +`fieldToVoltAbsDeviation`, absolute threshhold for how much the actual field amplitude is allowed to vary from the expected value in units of magnetic field, to still accept the system as safe, e.g. "5mT" +`fieldToVoltRelDeviation`, relative threshhold for allowed deviation +*Values will be used as rtol and atol of `isapprox`* + +**Old parameter:** \ +`controlPause` \ +**Replaced by:** \ +`timeUntilStable`, time in s to wait before evaluating the feedback signals after ramping + +**Changed parameters:** \ +`phaseAccuracy`, is now a Unitful value, specify e.g. "0.1°" + +**Removed parameters:** \ +`correctCrossCoupling`, if a field has `decouple = true` the controller will correct the cross coupling + + +#### Calibrations +Because the feedback calibration (or transfer function) is now a complex value it is possible to include the phase shift between feedback and field into this number. This allows the field phase in the Sequence.toml to be set to the desired/nominal value ("0.0rad" or "sin" for sine excitation and "pi/2*rad" or "cos" for cosine excitation) instead of correcting for the feedback phase shift using that phase value. \ No newline at end of file diff --git a/docs/src/framework/devices.md b/docs/src/framework/devices.md index 521d121d..96652792 100644 --- a/docs/src/framework/devices.md +++ b/docs/src/framework/devices.md @@ -20,7 +20,7 @@ The fields of each `Device` can be grouped into three parts. ### Common Device Fields Every `Device` must have the fields `deviceID, params, present` and `dependencies`, as these are the fields used during automatic instantiation of a `Scanner`. -The `deviceID` is the name of a specific `Device` instance and corresponds to the name/key used in the `Scanner.toml`. The `params` field contains the user provided configuration parameters (see [Device Parameter](@ref)). +The `deviceID` is the name of a specific `Device` instance and corresponds to the name/key used in the `Scanner.toml`. The `params` field contains the user provided configuration parameters (see [Device Parameter Field](@ref)). Lastly the `present` field denotes if a `Device` was succesfully initialized and the `dependencies` field contains a `Dict` containing all the dependent `Devices` of the current `Device`. diff --git a/docs/src/framework/scanner.md b/docs/src/framework/scanner.md index 8d726f3b..edeb5189 100644 --- a/docs/src/framework/scanner.md +++ b/docs/src/framework/scanner.md @@ -34,7 +34,7 @@ The `Scanner.toml` contains the configuration parameters of the `Scanner` and is ### General Section The general section contains the details of the scanner. The fields correspond to the `scanner` group of the [MPI data format (MDF)](https://github.com/MagneticParticleImaging/MDF) and are used when writing a measurement to disk. -All fields that have units will be parsed with [Unitful](https://github.com/PainterQubits/Unitful.jl) and should therefore be denoted as strings with the unit attached without a space. This also applies to the [Devices section](@ref). +All fields that have units will be parsed with [Unitful](https://github.com/PainterQubits/Unitful.jl) and should therefore be denoted as strings with the unit attached without a space. This also applies to the [Device Section](@ref). ```toml [General] @@ -46,20 +46,17 @@ topology = "" gradient = "XXT/m" ``` -### Runtime Section - -The protocol section contains hints for `Protocol`, scripts and GUI implementations, such as on which Julia threads to run certain threads or which default `Sequences` to display. +In addition, the General section contains hints for `Protocol`, scripts and GUI implementations, such as on which Julia threads to run certain threads or which default `Sequences` to display. ```toml -[Runtime] defaultProtocol = "" -transferFunction = "" -dataStorage = " +datasetStore = " # Which Julia threads to run common tasks on +producerThreadID = 1 protocolThreadID = 2 -producerThreadID = 3 -consumerThreadID = 4 -serialThreadID = 5 + +consumerThreadID = 3 +serialThreadID = 4 ``` ### Device Section diff --git a/docs/src/framework/sequences.md b/docs/src/framework/sequences.md index 15a849f5..1c6f39b3 100644 --- a/docs/src/framework/sequences.md +++ b/docs/src/framework/sequences.md @@ -14,6 +14,7 @@ The general section contains a description string for the sequence, as well as t ```toml [General] +name = "" description = "" targetScanner = "" baseFrequency = "125MHz" diff --git a/docs/src/lib/base/protocols/mpimeasurement.md b/docs/src/lib/base/protocols/mpimeasurement.md deleted file mode 100644 index fc036e26..00000000 --- a/docs/src/lib/base/protocols/mpimeasurement.md +++ /dev/null @@ -1,5 +0,0 @@ -# MPIMeasurementProtocol - -```@docs -MPIMeasurements.MPIMeasurementProtocolParams -``` \ No newline at end of file diff --git a/src/Devices/DAQ/DAQ.jl b/src/Devices/DAQ/DAQ.jl index 6edf0a4c..e9ab1e7e 100644 --- a/src/Devices/DAQ/DAQ.jl +++ b/src/Devices/DAQ/DAQ.jl @@ -86,6 +86,11 @@ function createDAQChannels(::Type{DAQHBridge}, dict::Dict{String, Any}) return DAQHBridge{N}(;splattingDict...) end +""" +Parameters for a general DAQTxChannel + +$FIELDS +""" Base.@kwdef mutable struct DAQTxChannelParams <: TxChannelParams channelIdx::Int64 limitPeak::typeof(1.0u"V") @@ -96,8 +101,15 @@ Base.@kwdef mutable struct DAQTxChannelParams <: TxChannelParams calibration::Union{TransferFunction, String, Nothing} = nothing end +""" +Parameters for a general DAQRxChannel + +$FIELDS +""" Base.@kwdef mutable struct DAQRxChannelParams <: RxChannelParams + "required, Integer, channel idx of the corresponding DAQ" channelIdx::Int64 + "optional, can be either a filename to a .h5 file in the TransferFunctions folder or a single (complex) number" transferFunction::Union{TransferFunction, String, Nothing} = nothing end diff --git a/src/Devices/DAQ/RedPitayaDAQ.jl b/src/Devices/DAQ/RedPitayaDAQ.jl index f8fba908..548dd3ec 100644 --- a/src/Devices/DAQ/RedPitayaDAQ.jl +++ b/src/Devices/DAQ/RedPitayaDAQ.jl @@ -2,6 +2,11 @@ export RampingMode, NONE, HOLD, STARTUP @enum RampingMode NONE HOLD STARTUP export RedPitayaDAQParams +""" +Parameters for a DAQ of type `RedPitayaDAQ` + +$(FIELDS) +""" Base.@kwdef mutable struct RedPitayaDAQParams <: DAQParams "All configured channels of this DAQ device." channels::Dict{String, DAQChannelParams} @@ -21,6 +26,11 @@ Base.@kwdef mutable struct RedPitayaDAQParams <: DAQParams counterTriggerSourceChannel::DIOPins = DIO7_P end +""" +Parameters for a TxChannel of type RedPitayaLUTChannel + +$(FIELDS) +""" Base.@kwdef struct RedPitayaLUTChannelParams <: TxChannelParams channelIdx::Int64 calibration::Union{typeof(1.0u"V/T"), typeof(1.0u"V/A"), Nothing} = nothing diff --git a/src/Devices/Robots/CollisionModule/SimpleBoreCollisionModule.jl b/src/Devices/Robots/CollisionModule/SimpleBoreCollisionModule.jl index 4202a16b..da6abda6 100644 --- a/src/Devices/Robots/CollisionModule/SimpleBoreCollisionModule.jl +++ b/src/Devices/Robots/CollisionModule/SimpleBoreCollisionModule.jl @@ -1,5 +1,10 @@ export SimpleBoreCollisionModule, SimpleBoreCollisionModuleParams +""" +Parameters for a `SimpleBoreCollisionModule`` + +$FIELDS +""" Base.@kwdef struct SimpleBoreCollisionModuleParams <: DeviceParams "Diameter of scanner in the y-z plane" scannerDiameter::typeof(1.0u"mm") diff --git a/src/Devices/Robots/IgusRobot.jl b/src/Devices/Robots/IgusRobot.jl index 348585b7..acc1836c 100644 --- a/src/Devices/Robots/IgusRobot.jl +++ b/src/Devices/Robots/IgusRobot.jl @@ -31,6 +31,11 @@ const MODES = Dict( "PROFILE_POSITION" => 1, "HOMING" => 6) +""" +Parameters for a Robot of type `IgusRobot` + +$FIELDS +""" Base.@kwdef struct IgusRobotParams <: DeviceParams defaultVelocity::Vector{typeof(1.0u"mm/s")} = [10.0u"mm/s"] axisRange::Vector{Vector{typeof(1.0u"mm")}} = [[0,500.0]]u"mm" diff --git a/src/Devices/Robots/IselRobot.jl b/src/Devices/Robots/IselRobot.jl index bc42ec58..44f42dd9 100644 --- a/src/Devices/Robots/IselRobot.jl +++ b/src/Devices/Robots/IselRobot.jl @@ -31,6 +31,11 @@ const iselErrorCodes = Dict( abstract type IselRobotParams <: DeviceParams end +""" +Parameters for a Robot of type `IselRobot` that is connected directly via a serial port + +$FIELDS +""" Base.@kwdef struct IselRobotPortParams <: IselRobotParams axisRange::Vector{Vector{typeof(1.0u"mm")}} = [[0,420],[0,420],[0,420]]u"mm" defaultVel::Vector{typeof(1.0u"mm/s")} = [10,10,10]u"mm/s" @@ -54,6 +59,11 @@ Base.@kwdef struct IselRobotPortParams <: IselRobotParams end IselRobotPortParams(dict::Dict) = params_from_dict(IselRobotPortParams, prepareRobotDict(dict)) +""" +Parameters for a Robot of type `IselRobot` that is connected via a serial port pool + +$FIELDS +""" Base.@kwdef struct IselRobotPoolParams <: IselRobotParams axisRange::Vector{Vector{typeof(1.0u"mm")}} = [[0,420],[0,420],[0,420]]u"mm" defaultVel::Vector{typeof(1.0u"mm/s")} = [10,10,10]u"mm/s" diff --git a/src/Devices/Virtual/TxDAQController.jl b/src/Devices/Virtual/TxDAQController.jl index 67f05440..8ec7c5a0 100644 --- a/src/Devices/Virtual/TxDAQController.jl +++ b/src/Devices/Virtual/TxDAQController.jl @@ -1,16 +1,30 @@ export TxDAQControllerParams, TxDAQController, controlTx +""" +Parameters for a `TxDAQController`` + +$FIELDS +""" Base.@kwdef mutable struct TxDAQControllerParams <: DeviceParams + "Angle, required, allowed deviation of the excitation phase" phaseAccuracy::typeof(1.0u"rad") + "Number, required, allowed relative deviation of the excitation amplitude" relativeAmplitudeAccuracy::Float64 + "Magnetic field, default: 50µT, allowed absolute deviation of the excitation amplitude" absoluteAmplitudeAccuracy::typeof(1.0u"T") = 50.0u"µT" + "Integer, default: 20, maximum number of steps to try to control the system" maxControlSteps::Int64 = 20 - #fieldToVoltDeviation::Float64 = 0.2 + "Bool, default: false, control the DC value of the excitation field (only posible for DC enabled DF amplifiers)" controlDC::Bool = false + "Float, default: 0.0, time in seconds to wait before the DF is stable after ramping" timeUntilStable::Float64 = 0.0 + "Float, default: 0.002, time in seconds that the DF should be averaged during the control measurement" minimumStepDuration::Float64 = 0.002 + "Float, default: 0.2, relative deviation allowed between forward calibration and actual system state" fieldToVoltRelDeviation::Float64 = 0.2 + "Magnetic field, default: 5.0mT, absolute deviation allowed between forward calibration and actual system state" fieldToVoltAbsDeviation::typeof(1.0u"T") = 5.0u"mT" + "Magnetic field, default: 40mT, maximum field amplitude that the controller should allow" maxField::typeof(1.0u"T") = 40.0u"mT" end TxDAQControllerParams(dict::Dict) = params_from_dict(TxDAQControllerParams, dict) diff --git a/src/Protocols/ContinousMeasurementProtocol.jl b/src/Protocols/ContinousMeasurementProtocol.jl index e4618a6c..49318d3e 100644 --- a/src/Protocols/ContinousMeasurementProtocol.jl +++ b/src/Protocols/ContinousMeasurementProtocol.jl @@ -1,6 +1,8 @@ export ContinousMeasurementProtocol, ContinousMeasurementProtocolParams """ -Parameters for the MPIMeasurementProtocol +Parameters for the `ContinousMeasurementProtocol`` + +$FIELDS """ Base.@kwdef mutable struct ContinousMeasurementProtocolParams <: ProtocolParams "Foreground frames to measure. Overwrites sequence frames" diff --git a/src/Protocols/MPIForceProtocol.jl b/src/Protocols/MPIForceProtocol.jl index b32470bc..9954bad2 100644 --- a/src/Protocols/MPIForceProtocol.jl +++ b/src/Protocols/MPIForceProtocol.jl @@ -1,6 +1,8 @@ export MPIForceProtocol, MPIForceProtocolParams """ -Parameters for the MPIForceProtocol +Parameters for the `MPIForceProtocol` + +$FIELDS """ Base.@kwdef mutable struct MPIForceProtocolParams <: ProtocolParams "If set the tx amplitude and phase will be set with control steps" diff --git a/src/Protocols/MPIMeasurementProtocol.jl b/src/Protocols/MPIMeasurementProtocol.jl index 435c6370..ad8d1e99 100644 --- a/src/Protocols/MPIMeasurementProtocol.jl +++ b/src/Protocols/MPIMeasurementProtocol.jl @@ -1,6 +1,8 @@ export MPIMeasurementProtocol, MPIMeasurementProtocolParams """ -Parameters for the MPIMeasurementProtocol +Parameters for the `MPIMeasurementProtocol` + +$FIELDS """ Base.@kwdef mutable struct MPIMeasurementProtocolParams <: ProtocolParams "Foreground frames to measure. Overwrites sequence frames" diff --git a/src/Protocols/MPSMeasurementProtocol.jl b/src/Protocols/MPSMeasurementProtocol.jl index ce791631..dde61073 100644 --- a/src/Protocols/MPSMeasurementProtocol.jl +++ b/src/Protocols/MPSMeasurementProtocol.jl @@ -1,6 +1,8 @@ export MPSMeasurementProtocol, MPSMeasurementProtocolParams """ -Parameters for the MPSMeasurementProtocol +Parameters for the `MPSMeasurementProtocol` + + $FIELDS """ Base.@kwdef mutable struct MPSMeasurementProtocolParams <: ProtocolParams "Foreground frames to measure. Overwrites sequence frames" @@ -13,9 +15,9 @@ Base.@kwdef mutable struct MPSMeasurementProtocolParams <: ProtocolParams measureBackground::Bool = false "Remember background measurement" rememberBGMeas::Bool = false - "Tracer that is being used for the measurement" + #"Tracer that is being used for the measurement" #tracer::Union{Tracer, Nothing} = nothing - "If the temperature should be safed or not" + "If the temperature should be saved or not" saveTemperatureData::Bool = false "Sequence to measure" sequence::Union{Sequence, Nothing} = nothing @@ -23,9 +25,8 @@ Base.@kwdef mutable struct MPSMeasurementProtocolParams <: ProtocolParams sortPatches::Bool = true "Flag if the measurement should be saved as a system matrix or not" saveInCalibFolder::Bool = false - "Number of periods per offset of the MPS offset measurement. Overwrites parts of the sequence definition." - dfPeriodsPerOffset::Integer = 2 + dfPeriodsPerOffset::Integer = 50 "If true all periods per offset are averaged" averagePeriodsPerOffset::Bool = true end diff --git a/src/Protocols/MechanicalMPIMeasurementProtocol.jl b/src/Protocols/MechanicalMPIMeasurementProtocol.jl index 9cab5269..87cb7eb0 100644 --- a/src/Protocols/MechanicalMPIMeasurementProtocol.jl +++ b/src/Protocols/MechanicalMPIMeasurementProtocol.jl @@ -1,6 +1,8 @@ export MechanicalMPIMeasurementProtocol, MechanicalMPIMeasurementProtocolParams """ -Parameters for the MechanicalMPIMeasurementProtocol +Parameters for the `MechanicalMPIMeasurementProtocol` + +$FIELDS """ Base.@kwdef mutable struct MechanicalMPIMeasurementProtocolParams <: ProtocolParams "Foreground frames to measure. Overwrites sequence frames" diff --git a/src/Protocols/MultiSequenceSystemMatrixProtocol.jl b/src/Protocols/MultiSequenceSystemMatrixProtocol.jl index f7885edf..16b63b5b 100644 --- a/src/Protocols/MultiSequenceSystemMatrixProtocol.jl +++ b/src/Protocols/MultiSequenceSystemMatrixProtocol.jl @@ -1,6 +1,8 @@ export MultiSequenceSystemMatrixProtocol, MultiSequenceSystemMatrixProtocolParams """ -Parameters for the MultiSequenceSystemMatrixProtocol +Parameters for the `MultiSequenceSystemMatrixProtocol` + +$FIELDS """ Base.@kwdef mutable struct MultiSequenceSystemMatrixProtocolParams <: ProtocolParams "If set the tx amplitude and phase will be set with control steps" diff --git a/src/Protocols/RobotBasedMagneticFieldStaticProtocol.jl b/src/Protocols/RobotBasedMagneticFieldStaticProtocol.jl index b7617b45..2aadecb3 100644 --- a/src/Protocols/RobotBasedMagneticFieldStaticProtocol.jl +++ b/src/Protocols/RobotBasedMagneticFieldStaticProtocol.jl @@ -1,5 +1,10 @@ export RobotBasedMagneticFieldStaticProtocolParams, RobotBasedMagneticFieldStaticProtocol, measurement +""" +Parameter for the `RobotBasedMagneticFieldStaticProtocol` + +$FIELDS +""" Base.@kwdef mutable struct RobotBasedMagneticFieldStaticProtocolParams <: RobotBasedProtocolParams sequence::Union{Sequence, Nothing} = nothing positions::Union{GridPositions, Nothing} = nothing diff --git a/src/Protocols/RobotBasedSystemMatrixProtocol.jl b/src/Protocols/RobotBasedSystemMatrixProtocol.jl index dcd08fea..f69a8af5 100644 --- a/src/Protocols/RobotBasedSystemMatrixProtocol.jl +++ b/src/Protocols/RobotBasedSystemMatrixProtocol.jl @@ -1,6 +1,8 @@ export RobotBasedSystemMatrixProtocol, RobotBasedSystemMatrixProtocolParams """ -Parameters for the RobotBasedSystemMatrixProtocol +Parameters for the `RobotBasedSystemMatrixProtocol` + +$FIELDS """ Base.@kwdef mutable struct RobotBasedSystemMatrixProtocolParams <: RobotBasedProtocolParams "Minimum wait time between robot movements" diff --git a/src/Protocols/RobotBasedTDesignFieldProtocol.jl b/src/Protocols/RobotBasedTDesignFieldProtocol.jl index ef4da9dd..8448b525 100644 --- a/src/Protocols/RobotBasedTDesignFieldProtocol.jl +++ b/src/Protocols/RobotBasedTDesignFieldProtocol.jl @@ -1,5 +1,10 @@ export RobotBasedTDesignFieldProtocolParams, RobotBasedTDesignFieldProtocol, measurement +""" +Parameters for the `RobotBasedTDesignFieldProtocol` + +$FIELDS +""" Base.@kwdef mutable struct RobotBasedTDesignFieldProtocolParams <: RobotBasedProtocolParams sequence::Union{Sequence, Nothing} = nothing radius::typeof(1.0u"mm") = 0.0u"mm" diff --git a/src/Protocols/RobotMPIMeasurementProtocol.jl b/src/Protocols/RobotMPIMeasurementProtocol.jl index 50ce1b8f..2d65c36b 100644 --- a/src/Protocols/RobotMPIMeasurementProtocol.jl +++ b/src/Protocols/RobotMPIMeasurementProtocol.jl @@ -1,6 +1,8 @@ export RobotMPIMeasurementProtocol, RobotMPIMeasurementProtocolParams """ -Parameters for the RobotMPIMeasurementProtocol +Parameters for the `RobotMPIMeasurementProtocol` + +$FIELDS """ Base.@kwdef mutable struct RobotMPIMeasurementProtocolParams <: ProtocolParams "Foreground position" diff --git a/src/Scanner.jl b/src/Scanner.jl index 01463675..8478a5c7 100644 --- a/src/Scanner.jl +++ b/src/Scanner.jl @@ -183,6 +183,8 @@ end General description of the scanner. Note: The fields correspond to the root section of an MDF file. + +$FIELDS """ Base.@kwdef struct MPIScannerGeneral "Bore size of the scanner." @@ -215,6 +217,8 @@ end $(SIGNATURES) Basic description of a scanner. + +$(FIELDS) """ mutable struct MPIScanner "Name of the scanner" diff --git a/src/Sequences/MagneticField.jl b/src/Sequences/MagneticField.jl index 1c5aa420..8dfbb2a9 100644 --- a/src/Sequences/MagneticField.jl +++ b/src/Sequences/MagneticField.jl @@ -5,6 +5,8 @@ Description of a magnetic field. The field can either be electromagnetically or mechanically changed. The mechanical movement of e.g. an iron yoke would be defined within two channels, one electrical and one mechanical. + +$FIELDS """ Base.@kwdef struct MagneticField "Unique ID of the field description." diff --git a/src/Sequences/PeriodicElectricalChannel.jl b/src/Sequences/PeriodicElectricalChannel.jl index 0b8e5f37..cc9b1c59 100644 --- a/src/Sequences/PeriodicElectricalChannel.jl +++ b/src/Sequences/PeriodicElectricalChannel.jl @@ -2,7 +2,11 @@ export PeriodicElectricalComponent, SweepElectricalComponent, PeriodicElectricalChannel, ArbitraryElectricalComponent -"Component of an electrical channel with periodic base function." +""" +Component of an electrical channel with periodic base function. + +$FIELDS +""" Base.@kwdef mutable struct PeriodicElectricalComponent <: ElectricalComponent id::AbstractString "Divider of the component." @@ -15,9 +19,13 @@ Base.@kwdef mutable struct PeriodicElectricalComponent <: ElectricalComponent waveform::Waveform = WAVEFORM_SINE end -"Sweepable component of an electrical channel with periodic base function. +""" +Sweepable component of an electrical channel with periodic base function. Note: Does not allow for changes in phase since this would make the switch -between frequencies difficult." +between frequencies difficult. + +$FIELDS +""" Base.@kwdef mutable struct SweepElectricalComponent <: ElectricalComponent "Divider of the component." divider::Vector{Integer} @@ -27,6 +35,11 @@ Base.@kwdef mutable struct SweepElectricalComponent <: ElectricalComponent waveform::Waveform = WAVEFORM_SINE end +""" +Arbitrary waveform component of an electrical channel with periodic base function defined by a vector of $(RedPitayaDAQServer._awgBufferSize) values. + +$FIELDS +""" Base.@kwdef mutable struct ArbitraryElectricalComponent <: ElectricalComponent id::AbstractString "Divider of the component." @@ -39,8 +52,12 @@ Base.@kwdef mutable struct ArbitraryElectricalComponent <: ElectricalComponent values::Vector{Float64} end -"""Electrical channel based on based on periodic base functions. Only the -PeriodicElectricalChannel counts for the cycle length calculation""" +""" +Electrical channel based on based on periodic base functions. Only the +PeriodicElectricalChannel counts for the cycle length calculation + +$FIELDS +""" Base.@kwdef mutable struct PeriodicElectricalChannel <: ElectricalTxChannel "ID corresponding to the channel configured in the scanner." id::AbstractString diff --git a/src/Sequences/ProtocolOffsetElectricalChannel.jl b/src/Sequences/ProtocolOffsetElectricalChannel.jl index 09ecba0c..cae9d58f 100644 --- a/src/Sequences/ProtocolOffsetElectricalChannel.jl +++ b/src/Sequences/ProtocolOffsetElectricalChannel.jl @@ -1,5 +1,10 @@ export ProtocolOffsetElectricalChannel +""" +Offset channnel that gets translated into the correct representation by the `MPSMeasurementProtocol` + +$FIELDS +""" Base.@kwdef mutable struct ProtocolOffsetElectricalChannel{T<:Union{typeof(1.0u"T"),typeof(1.0u"A"),typeof(1.0u"V")}} <: ProtocolTxChannel "ID corresponding to the channel configured in the scanner." id::AbstractString diff --git a/src/Sequences/StepwiseElectricalChannel.jl b/src/Sequences/StepwiseElectricalChannel.jl index 2c127847..83043811 100644 --- a/src/Sequences/StepwiseElectricalChannel.jl +++ b/src/Sequences/StepwiseElectricalChannel.jl @@ -1,6 +1,10 @@ export StepwiseElectricalChannel -"Electrical channel with a stepwise definition of values." +""" +Electrical channel with a stepwise definition of values. + +$FIELDS +""" Base.@kwdef mutable struct StepwiseElectricalChannel <: AcyclicElectricalTxChannel "ID corresponding to the channel configured in the scanner." id::AbstractString From 63df7367c7a4203d461390e3f31fdc5a0d009064 Mon Sep 17 00:00:00 2001 From: "Justin Ackers @ mpi05" Date: Fri, 5 Sep 2025 09:29:15 +0200 Subject: [PATCH 168/168] link to docs --- CHANGELOG.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec0dfb30..2532ce7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ## 0.6.0 ### Most Important Breaking Changes -- **Breaking**: There have been multiple changes to arguments in the Scanner.toml related to both devices and the scanner, please find a detailed migration guide [](here) +- **Breaking**: There have been multiple changes to arguments in the Scanner.toml related to both devices and the scanner, please find a detailed migration guide [https://magneticparticleimaging.github.io/MPIMeasurements.jl/dev/config/upgrade.html#v0.5-to-v0.6](here) - **Breaking**: the receive transfer function is now defined per receive channel instead of per scanner, this allows the sequence to flexibly select receive channels and assemble the correct TF ### Improved support for arbitrary waveform components @@ -31,7 +31,7 @@ New features include: - new algorithm ordering the channels to reduce total wait time - save data in proper system matrix format for hybrid reconstruction -### New MultiSequenceSMProtocol +### New MultiSequenceSystemMatrixProtocol Measures a (hybrid) system matrix that is defined by one `Sequence` per position, this can be used to flexibly vary any component of the field sequence like amplitudes, phases and offsets. The individual measurements will be saved together as frames in a joint MDF file. Between the individual measurements the system can be instructed to wait until the value of a temperature sensor is below a configurable threshhold. ### General Updates @@ -44,4 +44,5 @@ Measures a (hybrid) system matrix that is defined by one `Sequence` per position - re-included implementation for fiber optical temperature sensor (FOTemp.jl) - small fixes regarding different Isel robot versions - renamed the `saveAsSystemMatrix` parameter to `saveInCalibFolder` -- removed `defaultSequence` parameter from scanner as the sequence is always defined in the protocol \ No newline at end of file +- removed `defaultSequence` parameter from scanner as the sequence is always defined in the protocol +- small updates to documentation \ No newline at end of file