Skip to content

Commit 0f2d737

Browse files
committed
Add smooth option for sources in Blocks
- For `Sine`, `Cosine`, `ExpSine`, `Step`, `Ramp` Add Square and Triangular sources Add a non-zero start_time to smooth Sine, Ramp, ExpSine tests & non-zero `offset` to smooth ExpSine Add "almost" exact versions of Triangular and Square waves The formulae from https://en.wikipedia.org/wiki/Square_wave & https://en.wikipedia.org/wiki/Triangle_wave are modified to accomodate any amplitude, frequency, offset and start_time translate t on x-axis with start_time for square and triangular waves trigger workflow Merge branch 'vk/smooth_blocks' of https://github.com/ven-k/ModelingToolkitStandardLibrary.jl into vk/smooth_blocks thermal tests are no longer broken; so `@test_broken` -> `@test`
1 parent a2d79c9 commit 0f2d737

File tree

4 files changed

+422
-62
lines changed

4 files changed

+422
-62
lines changed

src/Blocks/Blocks.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export Abs, Sign, Sqrt, Sin, Cos, Tan, Asin, Acos, Atan, Atan2, Sinh, Cosh, Tanh
1616
export Log, Log10
1717
include("math.jl")
1818

19-
export Constant, Sine, Cosine, ContinuousClock, Ramp, Step, ExpSine
19+
export Constant, Sine, Cosine, ContinuousClock, Ramp, Step, ExpSine, Square, Triangular
2020
include("sources.jl")
2121

2222
export Limiter, DeadZone, SlewRateLimiter

src/Blocks/sources.jl

Lines changed: 218 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,57 @@
1+
# Define and register smooth functions
2+
# These are "smooth" aka differentiable and avoid Gibbs effect
3+
# These follow: `offset` + `smooth_wave` * `smooth_step` with zero output for `t < start_time`
4+
function smooth_cos(x, δ, f, amplitude, ϕ, offset, start_time)
5+
offset + amplitude * cos(2*π*f*(x - start_time) + ϕ) * smooth_step(x, δ, one(x), zero(x), start_time)
6+
end
7+
8+
function smooth_damped_sin(x, δ, f, amplitude, damping, ϕ, offset, start_time)
9+
offset + exp((start_time - x)*damping)*amplitude*sin(2*π*f*(x - start_time) + ϕ) * smooth_step(x, δ, one(x), zero(x), start_time)
10+
end
11+
12+
function smooth_ramp(x, δ, height, duration, offset, start_time)
13+
offset + height/(duration) * (smooth_xH(x, δ, start_time) - smooth_xH(x, δ, start_time+duration))
14+
end
15+
16+
function smooth_sin(x, δ, f, amplitude, ϕ, offset, start_time)
17+
offset + amplitude * sin(2*pi*f*(x - start_time) + ϕ) * smooth_step(x, δ, one(x), zero(x), start_time)
18+
end
19+
20+
function smooth_square(x, δ, f, amplitude, offset, start_time)
21+
offset + amplitude*2atan(sin(2π*(x - start_time)*f)/δ)/π * smooth_step(x, δ, one(x), zero(x), start_time)
22+
end
23+
24+
function smooth_step(x, δ, height, offset, start_time)
25+
offset + height*(atan((x - start_time)/δ)/π + 0.5)
26+
end
27+
28+
function smooth_triangular(x, δ, f, amplitude, offset, start_time)
29+
offset + amplitude * (1-2acos((1 - δ)sin(2π*(x - start_time)*f))/π) * smooth_step(x, δ, one(x), zero(x), start_time)
30+
end
31+
32+
function smooth_xH(x, δ, tₒ)
33+
0.5*(x-tₒ) * (1+((x-tₒ)/sqrt((x-tₒ)^2+δ^2)))
34+
end
35+
36+
function square(x, f, amplitude, offset, start_time)
37+
offset + (x > start_time) * (amplitude * (4*floor(f*(x - start_time)) - 2*floor(2*(x - start_time)*f) + 1))
38+
end
39+
40+
function triangular(x, f, amplitude, offset, start_time)
41+
p = 1/f # period
42+
offset + (x > start_time) * (4 * amplitude * f * abs(abs((x - p/4 - start_time) % p) - p/2) - amplitude)
43+
end
44+
45+
@register_symbolic smooth_cos(x, δ, f, amplitude, ϕ, offset, start_time)
46+
@register_symbolic smooth_damped_sin(x, δ, f, amplitude, damping, ϕ, offset, start_time)
47+
@register_symbolic smooth_ramp(x, δ, height, duration, offset, start_time)
48+
@register_symbolic smooth_sin(x, δ, f, amplitude, ϕ, offset, start_time)
49+
@register_symbolic smooth_square(x, δ, f, amplitude, offset, start_time)
50+
@register_symbolic smooth_step(x, δ, height, offset, start_time)
51+
@register_symbolic smooth_triangular(x, δ, f, amplitude, offset, start_time)
52+
@register_symbolic triangular(x, f, amplitude, offset, start_time)
53+
@register_symbolic square(x, f, amplitude, offset, start_time)
54+
155
"""
256
Generate constant signal.
357
@@ -22,25 +76,36 @@ Generate sine signal.
2276
# Parameters:
2377
- `frequency`: [Hz] Frequency of sine wave
2478
- `amplitude`: Amplitude of sine wave
25-
- `phase`: [rad] Phase of sine wave
79+
- `phase`: [rad] Phase of sine wave
2680
- `offset`: Offset of output signal
2781
- `start_time`: [s] Output `y = offset` for `t < start_time`
82+
- `smooth`: If `true`, returns a smooth wave. Defaults to `false`
83+
It uses a smoothing factor of `δ=1e-5`
2884
2985
# Connectors:
3086
- `output`
3187
"""
32-
function Sine(;name,
33-
frequency,
88+
function Sine(;name,
89+
frequency,
3490
amplitude=1,
3591
phase=0,
3692
offset=0,
37-
start_time=0)
93+
start_time=0,
94+
smooth=false)
3895

3996
@named output = RealOutput()
4097
pars = @parameters offset=offset start_time=start_time amplitude=amplitude frequency=frequency phase=phase
98+
equation = if smooth == false
99+
offset + ifelse(t < start_time, 0, amplitude* sin(2*pi*frequency*(t - start_time) + phase))
100+
else
101+
δ = 1e-5
102+
smooth_sin(t, δ, frequency, amplitude, phase, offset, start_time)
103+
end
104+
41105
eqs = [
42-
output.u ~ offset + ifelse(t < start_time, 0, amplitude* sin(2*pi*frequency*(t - start_time) + phase))
106+
output.u ~ equation
43107
]
108+
44109
compose(ODESystem(eqs, t, [], pars; name=name), [output])
45110
end
46111

@@ -50,32 +115,43 @@ Generate cosine signal.
50115
# Parameters:
51116
- `frequency`: [Hz] Frequency of sine wave
52117
- `amplitude`: Amplitude of sine wave
53-
- `phase`: [rad] Phase of sine wave
118+
- `phase`: [rad] Phase of sine wave
54119
- `offset`: Offset of output signal
55120
- `start_time`: [s] Output `y = offset` for `t < start_time`
121+
- `smooth`: If `true`, returns a smooth wave. Defaults to `false`
122+
It uses a smoothing factor of `δ=1e-5`
56123
57124
# Connectors:
58125
- `output`
59126
"""
60-
function Cosine(;name,
61-
frequency,
127+
128+
function Cosine(;name,
129+
frequency,
62130
amplitude=1,
63131
phase=0,
64132
offset=0,
65-
start_time=0)
133+
start_time=0,
134+
smooth=false)
66135

67136
@named output = RealOutput()
68137
pars = @parameters offset=offset start_time=start_time amplitude=amplitude frequency=frequency phase=phase
138+
equation = if smooth == false
139+
offset + ifelse(t < start_time, zero(t), amplitude* cos(2*pi*frequency*(t - start_time) + phase))
140+
else
141+
δ = 1e-5
142+
smooth_cos(t, δ, frequency, amplitude, phase, offset, start_time)
143+
end
69144
eqs = [
70-
output.u ~ offset + ifelse(t < start_time, 0, amplitude* cos(2*pi*frequency*(t - start_time) + phase))
145+
output.u ~ equation
71146
]
147+
72148
compose(ODESystem(eqs, t, [], pars; name=name), [output])
73149
end
74150

75151
"""
76152
Generate current time signal.
77153
78-
# Parameters:
154+
# Parameters:
79155
- `offset`: Offset of output signal
80156
- `start_time`: [s] Output `y = offset` for `t < start_time`
81157
@@ -86,8 +162,9 @@ function ContinuousClock(;name, offset=0, start_time=0)
86162
@named output = RealOutput()
87163
pars = @parameters offset=offset start_time=start_time
88164
eqs = [
89-
output.u ~ offset + ifelse(t < start_time, 0, t - start_time)
165+
output.u ~ offset + ifelse(t < start_time, zero(t), t - start_time)
90166
]
167+
91168
compose(ODESystem(eqs, t, [], pars; name=name), [output])
92169
end
93170

@@ -99,22 +176,73 @@ Generate ramp signal.
99176
- `duration`: [s] Duration of ramp (= 0.0 gives a Step)
100177
- `offset`: Offset of output signal
101178
- `start_time`: [s] Output `y = offset` for `t < start_time`
179+
- `smooth`: If `true`, returns a smooth wave. Defaults to `false`
180+
It uses a smoothing factor of `δ=1e-5`
102181
103182
# Connectors:
104183
- `output`
105184
"""
106-
function Ramp(;name,
107-
offset=0,
185+
function Ramp(;name,
108186
height=1,
109-
duration=1,
110-
start_time=0)
187+
duration=1,
188+
offset=0,
189+
start_time=0,
190+
smooth=false)
111191

112192
@named output = RealOutput()
113193
pars = @parameters offset=offset start_time=start_time height=height duration=duration
114-
eqs = [
115-
output.u ~ offset + ifelse(t < start_time, 0,
194+
equation = if smooth == false
195+
offset + ifelse(t < start_time, 0,
116196
ifelse(t < (start_time + duration), (t - start_time) * height / duration, height))
197+
else
198+
δ = 1e-5
199+
smooth_ramp(t, δ, height, duration, offset, start_time)
200+
end
201+
202+
eqs = [
203+
output.u ~ equation
204+
]
205+
206+
compose(ODESystem(eqs, t, [], pars; name=name), [output])
207+
end
208+
209+
"""
210+
Generate smooth square signal.
211+
212+
# Parameters:
213+
- `frequency`: [Hz] Frequency of square wave
214+
- `amplitude`: Amplitude of square wave
215+
- `offset`: Offset of output signal
216+
- `start_time`: [s] Output `y = offset` for `t < start_time`
217+
- `smooth`: If `true`, returns a smooth wave. Defaults to `false`
218+
It uses a smoothing factor of `δ=1e-5`
219+
220+
# Connectors:
221+
- `output`
222+
"""
223+
function Square(; name, frequency=1.0, amplitude=1.0,
224+
offset=0.0, start_time=0.0, smooth=false)
225+
δ = 1e-5
226+
227+
@named output = RealOutput()
228+
pars = @parameters begin
229+
frequency=frequency
230+
amplitude=amplitude
231+
offset=offset
232+
start_time=start_time
233+
end
234+
235+
equation = if smooth == false
236+
square(t, frequency, amplitude, offset, start_time)
237+
else
238+
δ = 1e-5
239+
smooth_square(t, δ, frequency, amplitude, offset, start_time)
240+
end
241+
242+
eqs = [
243+
output.u ~ equation
117244
]
245+
118246
compose(ODESystem(eqs, t, [], pars; name=name), [output])
119247
end
120248

@@ -125,16 +253,26 @@ Generate step signal.
125253
- `height`: Height of step
126254
- `offset`: Offset of output signal
127255
- `start_time`: [s] Output `y = offset` for `t < start_time`
256+
- `smooth`: If `true`, returns a smooth wave. Defaults to `false`
257+
It uses a smoothing factor of `δ=1e-5`
128258
129259
# Connectors:
130260
- `output`
131261
"""
132-
function Step(;name, offset=0, height=1, start_time=0)
262+
function Step(;name, height=1, offset=0, start_time=0, smooth=true)
133263
@named output = RealOutput()
134264
pars = @parameters offset=offset start_time=start_time height=height
265+
equation = if smooth == false
266+
offset + ifelse(t < start_time, zero(t), height)
267+
else
268+
δ = 1e-5
269+
smooth_step(t, δ, height, offset, start_time)
270+
end
271+
135272
eqs = [
136-
output.u ~ offset + ifelse(t < start_time, 0, height)
273+
output.u ~ equation
137274
]
275+
138276
compose(ODESystem(eqs, t, [], pars; name=name), [output])
139277
end
140278

@@ -145,26 +283,81 @@ Generate exponentially damped sine signal.
145283
- `frequency`: [Hz] Frequency of sine wave
146284
- `amplitude`: Amplitude of sine wave
147285
- `damping`: [1/s] Damping coefficient of sine wave
148-
- `phase`: [rad] Phase of sine wave
286+
- `phase`: [rad] Phase of sine wave
149287
- `offset`: Offset of output signal
150288
- `start_time`: [s] Output `y = offset` for `t < start_time`
289+
- `smooth`: If `true`, returns a smooth wave. Defaults to `false`
290+
It uses a smoothing factor of `δ=1e-5`
151291
152292
# Connectors:
153293
- `output`
154294
"""
155-
function ExpSine(;name,
156-
frequency,
295+
function ExpSine(; name,
296+
frequency,
157297
amplitude=1,
158298
damping=0.1,
159299
phase=0,
160300
offset=0,
161-
start_time=0)
301+
start_time=0,
302+
smooth=false)
162303

163304
@named output = RealOutput()
164305
pars = @parameters offset=offset start_time=start_time amplitude=amplitude frequency=frequency phase=phase damping=damping
306+
307+
equation = if smooth == false
308+
offset + ifelse(t < start_time, 0, amplitude * exp(-damping * (t - start_time)) * sin(2*pi*frequency*(t - start_time) + phase))
309+
else
310+
δ = 1e-5
311+
smooth_damped_sin(t, δ, frequency, amplitude, damping, phase, offset, start_time)
312+
end
313+
314+
eqs = [
315+
output.u ~ equation
316+
]
317+
318+
compose(ODESystem(eqs, t, [], pars; name=name), [output])
319+
end
320+
321+
"""
322+
Generate smooth triangular signal for frequencies less than or equal to 25 Hz
323+
324+
# Parameters:
325+
- `frequency`: [Hz] Frequency of square wave
326+
- `amplitude`: Amplitude of square wave
327+
- `offset`: Offset of output signal.
328+
- `start_time`: [s] Output `y = offset` for `t < start_time`
329+
- `smooth`: If `true`, returns a smooth wave. Defaults to `false`
330+
It uses a smoothing factor of `δ=1e-5`
331+
332+
# Connectors:
333+
- `output`
334+
"""
335+
function Triangular(; name, amplitude=1.0, frequency=1.0,
336+
offset=0.0, start_time=0.0, smooth=false)
337+
338+
if smooth
339+
frequency > 25 && @warn "`frequency > 25` can lead to non-triangular wave pattern"
340+
end
341+
342+
@named output = RealOutput()
343+
pars = @parameters begin
344+
amplitude=amplitude
345+
frequency=frequency
346+
offset=offset
347+
start_time=start_time
348+
end
349+
350+
equation = if smooth == false
351+
triangular(t, frequency, amplitude, offset, start_time)
352+
else
353+
δ = 1e-5
354+
smooth_triangular(t, δ, frequency, amplitude, offset, start_time)
355+
end
356+
165357
eqs = [
166-
output.u ~ offset + ifelse(t < start_time, 0, amplitude * exp(-damping * (t - start_time)) * sin(2*pi*frequency*(t - start_time) + phase))
358+
output.u ~ equation
167359
]
360+
168361
compose(ODESystem(eqs, t, [], pars; name=name), [output])
169362
end
170363

0 commit comments

Comments
 (0)