Skip to content

Commit b115a34

Browse files
authored
Merge pull request #21 from JuliaComputing/ADeffects
add Sampler with AD effects
2 parents a77915a + a8a5a65 commit b115a34

File tree

4 files changed

+119
-4
lines changed

4 files changed

+119
-4
lines changed

docs/src/tutorials/noise.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ z = ShiftIndex(Clock(0.1))
117117
@equations begin
118118
connect(input.output, quant.input)
119119
D(x) ~ 0 # Dummy equation
120+
# D(x) ~ Hold(quant.y) # Dummy equation
120121
end
121122
end
122123
@named m = QuantizationModel()
@@ -147,3 +148,38 @@ plot(u, [y_mr y_mt], label=["Midrise" "Midtread"], xlabel="Input", ylabel="Outpu
147148
Note how the default mid-rise quantizer mode has a rise at the middle of the interval, while the mid-tread mode has a flat region (a tread) centered around the middle of the interval.
148149

149150
The default option `midrise = true` includes both end points as possible output values, while `midrise = false` does not include the upper limit.
151+
152+
153+
## Sampling with AD effects
154+
The block [`SampleWithADEffects`](@ref) combines an ideal [`Sampler`](@ref), a [`NormalNoise](@ref) and a [`Quantization`](@ref) block to simulate the undesirable but practically occurring effects of sampling, noise and quantization in an AD converter. The block has the connectors `input` and `output`, where the input is the continuous-time signal to be sampled, and the output is the quantized, noisy signal. Example:
155+
156+
```@example QUANT
157+
@mtkmodel PracticalSamplerModel begin
158+
@components begin
159+
input = Sine(amplitude=1.2, frequency=1, smooth=false)
160+
sampling = SampleWithADEffects(; dt=0.03, bits=3, y_min = -1, y_max = 1, sigma = 0.1, noisy = true, quantized=true, midrise=true)
161+
end
162+
@variables begin
163+
x(t) = 0 # Dummy variable to work around a bug for models without continuous-time state
164+
end
165+
@equations begin
166+
connect(input.output, sampling.input)
167+
D(x) ~ Hold(sampling.y) # Dummy equation
168+
end
169+
end
170+
@named m = PracticalSamplerModel()
171+
m = complete(m)
172+
ssys = structural_simplify(IRSystem(m))
173+
prob = ODEProblem(ssys, [m.sampling.noise.y(z-1) => 0], (0.0, 2.0))
174+
sol = solve(prob, Tsit5())
175+
plot(sol, idxs=m.input.output.u)
176+
plot!(sol, idxs=m.sampling.y, label="AD converted output")
177+
```
178+
179+
Both quantization and noise addition are optional and turned off by default. In the example above, we turn them on with keywords `noisy = true` and `quantized = true`. The noise is Gaussian white noise with standard deviation `sigma`, and the quantization is a 3-bit midrise quantizer (8 output levels) with limits `y_min` and `y_max`. Limits have to be provided when quantization is used. The `dt` parameter is the sampling time, if left unspecified, it will be inferred from context.
180+
181+
Things to notice in the plot:
182+
- The sampled signal is saturated at the quantization limits ±1.
183+
- The noise is added to the signal before quantization, which means that the sampled signal has ``2^\text{bits}`` distinct output levels only.
184+
- 0 is not a possible output value. In situations where 0 is an important value (such as in the presence of integration of a quantized value that is expected to be close to 0), the mid-tread quantizer should be used instead by passing `midrise = false`.
185+

src/ModelingToolkitSampledData.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ using StableRNGs
55

66
export get_clock
77
export DiscreteIntegrator, DiscreteDerivative, Delay, Difference, ZeroOrderHold, Sampler,
8-
ClockChanger,
8+
ClockChanger, SampleWithADEffects,
99
DiscretePIDParallel, DiscretePIDStandard, DiscreteStateSpace,
1010
DiscreteTransferFunction, NormalNoise, UniformNoise, Quantization,
1111
DiscreteSlewRateLimiter, ExponentialFilter

src/discrete_blocks.jl

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -974,11 +974,9 @@ This block is not differentiable, the derivative is zero everywhere exect for at
974974
@parameters begin
975975
y_max = 1, [description = "Upper limit of output"]
976976
y_min = -1, [description = "Lower limit of output"]
977-
bits::Int = 8, [description = "Number of bits of quantization"]
977+
bits = 8, [description = "Number of bits of quantization"]
978978
quantized::Bool = true, [description = "If quantization effects shall be computed."]
979979
end
980-
begin
981-
end
982980
@equations begin
983981
y(z) ~ ifelse(quantized == true, quantize(u(z), bits, y_min, y_max, midrise), u(z))
984982
end
@@ -1080,4 +1078,58 @@ Discrete-time On-Off controller with hysteresis. The controller switches between
10801078
y(z) ~ k*(2*s(z) - 1)
10811079
end
10821080
end
1081+
end
1082+
1083+
"""
1084+
SampleWithADEffects(quantized = false, noisy = false)
1085+
1086+
A sampler with additional effects that appear in practical systems, such as measurement noise and quantization.
1087+
1088+
The operations occur in the order
1089+
1. Sampling
1090+
2. Noise addition
1091+
3. Quantization
1092+
1093+
# Structural parameters:
1094+
- `quantized`: If true, the output is quantized. When this option is used, the output is quantized to the number of bits specified by the `bits` parameter. The quantization is midrise if `midrise = true`, otherwise it is midtread. The output is also limited to the range `[y_min, y_max]`.
1095+
- `noisy`: If true, the output is corrupted by additive white Gaussian noise with standard deviation `sigma` (defaults to 0.1). If `noisy = false`, the noise block is a unit gain.
1096+
- `dt`: Sample interval of the sampler. If not specified, the sample interval is inferred from the clock of the system.
1097+
- `clock`: Clock signal of the system. If not specified, the sample interval is inferred from the clock of the system. If `clock` is specified, the parameter `dt` has no effect.
1098+
1099+
# Parameters:
1100+
- `y_min`: Lower limit of output, defaults to -1. Only used if `quantized = true`.
1101+
- `y_max`: Upper limit of output, defaults to 1. Only used if `quantized = true`.
1102+
- `bits`: Number of bits of quantization, defaults to 8 (256 output levels between `y_min` and `y_max`). Only used if `quantized = true`.
1103+
- `sigma`: Standard deviation of the additive Gaussian noise, defaults to 0.1. Only used if `noisy = true`.
1104+
"""
1105+
@mtkmodel SampleWithADEffects begin
1106+
@extend input, output = siso = SISO()
1107+
@structural_parameters begin
1108+
dt = nothing
1109+
clock = (dt === nothing ? InferredDiscrete() : Clock(dt))
1110+
midrise = true
1111+
quantized = false
1112+
noisy = false
1113+
end
1114+
@parameters begin
1115+
y_min = -1, [description = "Lower limit of output"]
1116+
y_max = 1, [description = "Upper limit of output"]
1117+
bits = 8, [description = "Number of bits of quantization"]
1118+
sigma = 0.1, [description = "Standard deviation of the additive noise"]
1119+
end
1120+
@components begin
1121+
sampler = Sampler(; clock)
1122+
if noisy
1123+
noise = NormalNoise(; sigma, additive = true)
1124+
else
1125+
noise = Gain(; k = 1)
1126+
end
1127+
quantization = Quantization(; bits, y_min, y_max, midrise, quantized)
1128+
end
1129+
@equations begin
1130+
connect(input, sampler.input)
1131+
connect(sampler.output, noise.input)
1132+
connect(noise.output, quantization.input)
1133+
connect(quantization.output, output)
1134+
end
10831135
end

test/test_discrete_blocks.jl

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,4 +538,31 @@ end
538538
@test sol(0.999, idxs=m.filter.y) == 0
539539
@test sol(1.1, idxs=m.filter.y) > 0
540540

541+
end
542+
543+
@testset "sampling with AD effects" begin
544+
@info "Testing sampling with AD effects"
545+
z = ShiftIndex()
546+
@mtkmodel PracticalSamplerModel begin
547+
@components begin
548+
input = Sine(amplitude=1.2, frequency=1, smooth=false)
549+
sampling = SampleWithADEffects(; dt=0.03, bits=3, y_min = -1, y_max = 1, sigma = 0.1, noisy = true, quantized=true, midrise=true)
550+
end
551+
@variables begin
552+
x(t) = 0 # Dummy variable to work around a bug for models without continuous-time state
553+
end
554+
@equations begin
555+
connect(input.output, sampling.input)
556+
D(x) ~ Hold(sampling.y) # Dummy equation
557+
end
558+
end
559+
@named m = PracticalSamplerModel()
560+
m = complete(m)
561+
ssys = structural_simplify(IRSystem(m))
562+
prob = ODEProblem(ssys, [m.sampling.noise.y(z-1) => 0], (0.0, 2.0))
563+
sol = solve(prob, Tsit5())
564+
565+
@test length(unique(sol[m.sampling.y])) == 8
566+
@test maximum(abs, sol(0:0.03:1, idxs=m.sampling.y) - sol(0:0.03:1, idxs=m.sampling.u)) < 0.3
567+
541568
end

0 commit comments

Comments
 (0)