|
| 1 | +using Symbolics: Operator, Num, Term, value, recursive_hasoperator |
| 2 | + |
| 3 | +# Shift |
| 4 | + |
| 5 | +""" |
| 6 | +$(TYPEDEF) |
| 7 | +
|
| 8 | +Represents a shift operator. |
| 9 | +
|
| 10 | +# Fields |
| 11 | +$(FIELDS) |
| 12 | +
|
| 13 | +# Examples |
| 14 | +
|
| 15 | +```jldoctest |
| 16 | +julia> using Symbolics |
| 17 | +
|
| 18 | +julia> @variables t; |
| 19 | +
|
| 20 | +julia> Δ = Shift(t) |
| 21 | +(::Shift) (generic function with 2 methods) |
| 22 | +``` |
| 23 | +""" |
| 24 | +struct Shift <: Operator |
| 25 | + """Fixed Shift""" |
| 26 | + t::Any |
| 27 | + steps::Int |
| 28 | + Shift(t, steps = 1) = new(value(t), steps) |
| 29 | +end |
| 30 | +function (D::Shift)(x, allow_zero = false) |
| 31 | + !allow_zero && D.steps == 0 && return x |
| 32 | + Term{symtype(x)}(D, Any[x]) |
| 33 | +end |
| 34 | +function (D::Shift)(x::Num, allow_zero = false) |
| 35 | + !allow_zero && D.steps == 0 && return x |
| 36 | + vt = value(x) |
| 37 | + if istree(vt) |
| 38 | + op = operation(vt) |
| 39 | + if op isa Shift |
| 40 | + if isequal(D.t, op.t) |
| 41 | + arg = arguments(vt)[1] |
| 42 | + newsteps = D.steps + op.steps |
| 43 | + return Num(newsteps == 0 ? arg : Shift(D.t, newsteps)(arg)) |
| 44 | + end |
| 45 | + end |
| 46 | + end |
| 47 | + Num(D(vt, allow_zero)) |
| 48 | +end |
| 49 | +SymbolicUtils.promote_symtype(::Shift, t) = t |
| 50 | + |
| 51 | +Base.show(io::IO, D::Shift) = print(io, "Shift(", D.t, ", ", D.steps, ")") |
| 52 | + |
| 53 | +Base.:(==)(D1::Shift, D2::Shift) = isequal(D1.t, D2.t) && isequal(D1.steps, D2.steps) |
| 54 | +Base.hash(D::Shift, u::UInt) = hash(D.steps, hash(D.t, xor(u, 0x055640d6d952f101))) |
| 55 | + |
| 56 | +Base.:^(D::Shift, n::Integer) = Shift(D.t, D.steps * n) |
| 57 | +Base.literal_pow(f::typeof(^), D::Shift, ::Val{n}) where {n} = Shift(D.t, D.steps * n) |
| 58 | + |
| 59 | +hasshift(eq::Equation) = hasshift(eq.lhs) || hasshift(eq.rhs) |
| 60 | + |
| 61 | +""" |
| 62 | + hasshift(O) |
| 63 | +
|
| 64 | +Returns true if the expression or equation `O` contains [`Shift`](@ref) terms. |
| 65 | +""" |
| 66 | +hasshift(O) = recursive_hasoperator(Shift, O) |
| 67 | + |
| 68 | +# Sample |
| 69 | + |
| 70 | +""" |
| 71 | +$(TYPEDEF) |
| 72 | +
|
| 73 | +Represents a sample operator. A discrete-time signal is created by sampling a continuous-time signal. |
| 74 | +
|
| 75 | +# Constructors |
| 76 | +`Sample(clock::TimeDomain = InferredDiscrete())` |
| 77 | +`Sample(t, dt::Real)` |
| 78 | +
|
| 79 | +`Sample(x::Num)`, with a single argument, is shorthand for `Sample()(x)`. |
| 80 | +
|
| 81 | +# Fields |
| 82 | +$(FIELDS) |
| 83 | +
|
| 84 | +# Examples |
| 85 | +
|
| 86 | +```jldoctest |
| 87 | +julia> using Symbolics |
| 88 | +
|
| 89 | +julia> @variables t; |
| 90 | +
|
| 91 | +julia> Δ = Sample(t, 0.01) |
| 92 | +(::Sample) (generic function with 2 methods) |
| 93 | +``` |
| 94 | +""" |
| 95 | +struct Sample <: Operator |
| 96 | + clock::Any |
| 97 | + Sample(clock::TimeDomain = InferredDiscrete()) = new(clock) |
| 98 | + Sample(t, dt::Real) = new(Clock(t, dt)) |
| 99 | +end |
| 100 | +Sample(x) = Sample()(x) |
| 101 | +(D::Sample)(x) = Term{symtype(x)}(D, Any[x]) |
| 102 | +(D::Sample)(x::Num) = Num(D(value(x))) |
| 103 | +SymbolicUtils.promote_symtype(::Sample, x) = x |
| 104 | + |
| 105 | +Base.show(io::IO, D::Sample) = print(io, "Sample(", D.clock, ")") |
| 106 | + |
| 107 | +Base.:(==)(D1::Sample, D2::Sample) = isequal(D1.clock, D2.clock) |
| 108 | +Base.hash(D::Sample, u::UInt) = hash(D.clock, xor(u, 0x055640d6d952f101)) |
| 109 | + |
| 110 | +""" |
| 111 | + hassample(O) |
| 112 | +
|
| 113 | +Returns true if the expression or equation `O` contains [`Sample`](@ref) terms. |
| 114 | +""" |
| 115 | +hassample(O) = recursive_hasoperator(Sample, O) |
| 116 | + |
| 117 | +# Hold |
| 118 | + |
| 119 | +""" |
| 120 | +$(TYPEDEF) |
| 121 | +
|
| 122 | +Represents a hold operator. A continuous-time signal is produced by holding a discrete-time signal `x` with zero-order hold. |
| 123 | +
|
| 124 | +``` |
| 125 | +cont_x = Hold()(disc_x) |
| 126 | +``` |
| 127 | +""" |
| 128 | +struct Hold <: Operator |
| 129 | +end |
| 130 | +(D::Hold)(x) = Term{symtype(x)}(D, Any[x]) |
| 131 | +(D::Hold)(x::Num) = Num(D(value(x))) |
| 132 | +SymbolicUtils.promote_symtype(::Hold, x) = x |
| 133 | + |
| 134 | +Hold(x) = Hold()(x) |
| 135 | + |
| 136 | +""" |
| 137 | + hashold(O) |
| 138 | +
|
| 139 | +Returns true if the expression or equation `O` contains [`Hold`](@ref) terms. |
| 140 | +""" |
| 141 | +hashold(O) = recursive_hasoperator(Hold, O) |
| 142 | + |
| 143 | +# ShiftIndex |
| 144 | + |
| 145 | +""" |
| 146 | + ShiftIndex |
| 147 | +
|
| 148 | +The `ShiftIndex` operator allows you to index a signal and obtain a shifted discrete-time signal. If the signal is continuous-time, the signal is sampled before shifting. |
| 149 | +
|
| 150 | +# Examples |
| 151 | +``` |
| 152 | +julia> @variables t x(t); |
| 153 | +
|
| 154 | +julia> k = ShiftIndex(t, 0.1); |
| 155 | +
|
| 156 | +julia> x(k) # no shift |
| 157 | +x(t) |
| 158 | +
|
| 159 | +julia> x(k+1) # shift |
| 160 | +Shift(t, 1)(x(t)) |
| 161 | +``` |
| 162 | +""" |
| 163 | +struct ShiftIndex |
| 164 | + clock::TimeDomain |
| 165 | + steps::Int |
| 166 | + ShiftIndex(clock::TimeDomain = Inferred(), steps::Int = 0) = new(clock, steps) |
| 167 | + ShiftIndex(t::Num, dt::Real, steps::Int = 0) = new(Clock(t, dt), steps) |
| 168 | +end |
| 169 | + |
| 170 | +function (xn::Num)(k::ShiftIndex) |
| 171 | + @unpack clock, steps = k |
| 172 | + x = value(xn) |
| 173 | + t = clock.t |
| 174 | + # Verify that the independent variables of k and x match and that the expression doesn't have multiple variables |
| 175 | + vars = Symbolics.get_variables(x) |
| 176 | + length(vars) == 1 || |
| 177 | + error("Cannot shift a multivariate expression $x. Either create a new state and shift this, or shift the individual variables in the expression.") |
| 178 | + args = Symbolics.arguments(vars[]) # args should be one element vector with the t in x(t) |
| 179 | + length(args) == 1 || |
| 180 | + error("Cannot shift an expression with multiple independent variables $x.") |
| 181 | + isequal(args[], t) || |
| 182 | + error("Independent variable of $xn is not the same as that of the ShiftIndex $(k.t)") |
| 183 | + |
| 184 | + # d, _ = propagate_time_domain(xn) |
| 185 | + # if d != clock # this is only required if the variable has another clock |
| 186 | + # xn = Sample(t, clock)(xn) |
| 187 | + # end |
| 188 | + # QUESTION: should we return a variable with time domain set to k.clock? |
| 189 | + xn = setmetadata(xn, TimeDomain, k.clock) |
| 190 | + if steps == 0 |
| 191 | + return xn # x(k) needs no shift operator if the step of k is 0 |
| 192 | + end |
| 193 | + Shift(t, steps)(xn) # a shift of k steps |
| 194 | +end |
| 195 | + |
| 196 | +Base.:+(k::ShiftIndex, i::Int) = ShiftIndex(k.clock, k.steps + i) |
| 197 | +Base.:-(k::ShiftIndex, i::Int) = k + (-i) |
| 198 | + |
| 199 | +""" |
| 200 | + input_timedomain(op::Operator) |
| 201 | +
|
| 202 | +Return the time-domain type (`Continuous()` or `Discrete()`) that `op` operates on. |
| 203 | +""" |
| 204 | +function input_timedomain(s::Shift, arg = nothing) |
| 205 | + if has_time_domain(arg) |
| 206 | + return get_time_domain(arg) |
| 207 | + end |
| 208 | + InferredDiscrete() |
| 209 | +end |
| 210 | + |
| 211 | +""" |
| 212 | + output_timedomain(op::Operator) |
| 213 | +
|
| 214 | +Return the time-domain type (`Continuous()` or `Discrete()`) that `op` results in. |
| 215 | +""" |
| 216 | +function output_timedomain(s::Shift, arg = nothing) |
| 217 | + if has_time_domain(arg) |
| 218 | + return get_time_domain(arg) |
| 219 | + end |
| 220 | + InferredDiscrete() |
| 221 | +end |
| 222 | + |
| 223 | +input_timedomain(::Sample, arg = nothing) = Continuous() |
| 224 | +output_timedomain(s::Sample, arg = nothing) = s.clock |
| 225 | + |
| 226 | +function input_timedomain(h::Hold, arg = nothing) |
| 227 | + if has_time_domain(arg) |
| 228 | + return get_time_domain(arg) |
| 229 | + end |
| 230 | + InferredDiscrete() # the Hold accepts any discrete |
| 231 | +end |
| 232 | +output_timedomain(::Hold, arg = nothing) = Continuous() |
| 233 | + |
| 234 | +sampletime(op::Sample, arg = nothing) = sampletime(op.clock) |
| 235 | +sampletime(op::ShiftIndex, arg = nothing) = sampletime(op.clock) |
| 236 | + |
| 237 | +changes_domain(op) = isoperator(op, Union{Sample, Hold}) |
| 238 | + |
| 239 | +function output_timedomain(x) |
| 240 | + if isoperator(x, Operator) |
| 241 | + return output_timedomain(operation(x), arguments(x)[]) |
| 242 | + else |
| 243 | + throw(ArgumentError("$x of type $(typeof(x)) is not an operator expression")) |
| 244 | + end |
| 245 | +end |
| 246 | + |
| 247 | +function input_timedomain(x) |
| 248 | + if isoperator(x, Operator) |
| 249 | + return input_timedomain(operation(x), arguments(x)[]) |
| 250 | + else |
| 251 | + throw(ArgumentError("$x of type $(typeof(x)) is not an operator expression")) |
| 252 | + end |
| 253 | +end |
0 commit comments