多次调度。
Julia语言的一个关键特性是多重调度,一种基于所有函数参数类型选择方法的机制。 与面向对象语言不同,其中方法绑定到第一个参数(self),Julia允许您组合任意数量参数的类型,这为您设计抽象提供了特殊的灵活性。 这种方法非常适合工程建模,其中单个接口用于不同的物理域(电力,机械,液压等)。)经常是必需的。
所呈现的代码演示了创建用于建模电气和机械部件的类型层次结构,使用带约束的参数类型,以及扩展内置函数(+, *, ==, show, getindex 和其他人),以及方法冲突的解决。 整个代码是围绕"阻抗"的概念构建的,这是一个广义的特性,用欧姆表示电气元件,用牛顿秒表示机械元件。
1. 抽象类型的导入和层次结构
using LinearAlgebra
import Base: show, +, *, ==, zero, one, getindex, setindex!
abstract type AbstractComponent end
abstract type ElectricalComponent <: AbstractComponent end
abstract type MechanicalComponent <: AbstractComponent end
第一行连接模块 LinearAlgebra 使用矩阵并从 Base,我们将为我们的类型扩展(重新定义)。 接下来,定义抽象类型的层次结构: AbstractComponent -所有物理组件的根类型。 从它继承了两个子类型: ElectricalComponent 和 MechanicalComponent. 此层次结构允许您进一步编写适用于所有组件的方法,无论是特定于电气组件还是机械组件。
2. 参数类型 SystemState 与内部和外部构造函数
struct SystemState{T<:Real, N<:AbstractMatrix{T}}
values::N
time::Float64
function SystemState{T,N}(values::N, time::Float64) where {T<:Real, N<:AbstractMatrix{T}}
@assert time >= 0 "时间不能是负数"
new(values, time)
end
end
SystemState(values::AbstractMatrix{T}, time::Float64) where {T<:Real} =
SystemState{T, typeof(values)}(values, time)
SystemState(values::AbstractVector{T}, time::Float64) where {T<:Real} =
SystemState(reshape(values, length(values), 1), time)
SystemState -表示系统在某个时间点的状态的参数类型。 参数: T -元素类型(必须是子类型 Real), N -矩阵类型(子类型 AbstractMatrix{T}). 内部构造函数检查时间是否为非负数,并保存传递的值。 外部构造函数增加了便利性:第一个采用任意矩阵,第二个是向量,它会自动转换为列(一列的矩阵)。 这是一个使用多个构造函数创建具有不同输入方法的对象的示例。
3. 功能的扩展 show, getindex, setindex! 为 SystemState
function show(io::IO, s::SystemState)
println(io, "SystemState at t = ", s.time, " :")
show(io, s.values)
end
getindex(s::SystemState, i::Int, j::Int) = s.values[i, j]
getindex(s::SystemState, i::Int) = s.values[i]
setindex!(s::SystemState, v, i::Int, j::Int) = setindex!(s.values, v, i, j)
setindex!(s::SystemState, v, i::Int) = setindex!(s.values, v, i)
对于自定义类型 SystemState 我们在控制台中定义自己的显示(show),以便显示时间戳和状态值。 我们还实现了索引(getindex)和索引赋值(setindex!),将他们委派给外地 values. 这允许您将状态元素作为普通矩阵或向量访问,这使得代码更加自然。
4. 电气和机械部件的特定类型
struct Resistor{T<:Real} <: ElectricalComponent
resistance::T
function Resistor{T}(R::T) where {T<:Real}
@assert R > 0 "电阻应该是正的"
new(R)
end
end
Resistor(R::Real) = Resistor{typeof(R)}(R)
struct Capacitor{T<:Real} <: ElectricalComponent
capacitance::T
function Capacitor{T}(C::T) where {T<:Real}
@assert C > 0 "能力必须是积极的"
new(C)
end
end
Capacitor(C::Real) = Capacitor{typeof(C)}(C)
struct Inductor{T<:Real} <: ElectricalComponent
inductance::T
function Inductor{T}(L::T) where {T<:Real}
@assert L > 0 "电感必须是正的"
new(L)
end
end
Inductor(L::Real) = Inductor{typeof(L)}(L)
struct Mass{T<:Real} <: MechanicalComponent
mass::T
function Mass{T}(m::T) where {T<:Real}
@assert m > 0 "质量应该是积极的"
new(m)
end
end
Mass(m::Real) = Mass{typeof(m)}(m)
struct Spring{T<:Real} <: MechanicalComponent
stiffness::T
function Spring{T}(k::T) where {T<:Real}
@assert k > 0 "刚度应为正"
new(k)
end
end
Spring(k::Real) = Spring{typeof(k)}(k)
struct Damper{T<:Real} <: MechanicalComponent
damping::T
function Damper{T}(c::T) where {T<:Real}
@assert c >= 0 "阻尼不能为负"
new(c)
end
end
Damper(c::Real) = Damper{typeof(c)}(c)
每个组件被定义为参数 struct 一个字段存储参数的数值(电阻、电容等)。). 内部构造函数检查物理正确性(值的积极性)并创建一个实例。 外部构造函数允许您在不显式指定类型的情况下创建组件:例如, Resistor(100.0) 它将自动确定参数的类型。 继承自 ElectricalComponent 或 MechanicalComponent 允许它们在特定于域的方法中使用。
5. 功能 impedance -在行动中的多个调度
impedance(comp::AbstractComponent, ω::Real) = error("未实施 ", typeof(comp))
impedance(R::Resistor, ω::Real) = complex(R.resistance, 0.0)
impedance(C::Capacitor, ω::Real) = complex(0.0, -1.0 / (ω * C.capacitance))
impedance(L::Inductor, ω::Real) = complex(0.0, ω * L.inductance)
impedance(M::Mass, ω::Real) = complex(0.0, ω * M.mass)
impedance(S::Spring, ω::Real) = complex(0.0, -S.stiffness / ω)
impedance(D::Damper, ω::Real) = complex(D.damping, 0.0)
response(comp::AbstractComponent, input::SystemState, t::Float64) =
error("未实施 ", typeof(comp))
定义了一般函数 impedance,其必须针对每个特定组件实施。 如果未定义,则抛出错误。 然后,每种类型的方法如下。 请注意:Julia根据第一个参数的类型选择所需的方法(comp)是多个调度的一个例子,其中方法由所有参数确定(这里有两个)。 这使得通过简单地为它们定义一个方法来添加新类型的组件变得容易。 impedance. 功能 response 它被保留为存根,但在实际代码中它将使用 impedance 来计算对输入信号的响应。
6. 操作员扩展 +, *, ==, zero, one
function +(c1::T, c2::T) where {T<:ElectricalComponent}
return (c1, c2)
end
function *(c1::T, c2::T) where {T<:ElectricalComponent}
return (c1, c2)
end
+(c1::M, c2::M) where {M<:MechanicalComponent} = (c1, c2)
*(c1::M, c2::M) where {M<:MechanicalComponent} = (c1, c2)
==(c1::Resistor, c2::Resistor) = c1.resistance == c2.resistance
==(c1::Capacitor, c2::Capacitor) = c1.capacitance == c2.capacitance
==(c1::Inductor, c2::Inductor) = c1.inductance == c2.inductance
==(c1::Mass, c2::Mass) = c1.mass == c2.mass
==(c1::Spring, c2::Spring) = c1.stiffness == c2.stiffness
==(c1::Damper, c2::Damper) = c1.damping == c2.damping
zero(::SystemState{T,N}) where {T,N} = SystemState(zero(N), 0.0)
one(::Type{Resistor{T}}) where {T} = Resistor(one(T))
此处演示了标准运算符的重载。 + 和 * 对于电气和机械组件,到目前为止只返回两个组件的元组,它象征着串行和并行连接(在实际应用中,应该创建一个特殊的复合类型)。 == 为每个特定组件重新定义以比较参数值。 zero 定义为 SystemState -返回值为零且时间为0.0的状态。 one 为类型定义 Resistor -返回具有单个电阻的电阻(例如,对于电路中的单个元件有用)。
7. 方法的潜在冲突的演示
function parallel_impedance(a::ElectricalComponent, b::ElectricalComponent)
return 1.0 / (1.0 / impedance(a, 1.0) + 1.0 / impedance(b, 1.0))
end
println("检查方法的含糊不清(用于教育目的):")
println("你可以运行'测试。detect_ambiguities(EngineeringSimulation)`")
该块示出了用于电元件的并联阻抗的方法的示例。 在评论中(我们删除了),讨论了如果定义了过于笼统的方法,则存在歧义的可能性。 这里只剩下的代码演示了Julia如何警告冲突,以及如何使用 Test.detect_ambiguities. 考虑方法的潜在冲突在设计过程中至关重要。
8. RLC电路和机械振荡器的仿真
function total_impedance(components::Vector{<:AbstractComponent}, ω::Real)
Z = 0.0im
for comp in components
Z += impedance(comp, ω)
end
return Z
end
R = Resistor(100.0)
C = Capacitor(1e-6)
L = Inductor(0.1)
components_elec = [R, C, L]
ω = 2π * 50
Z_total = total_impedance(components_elec, ω)
println("\N电RLC电路:")
println(" 组件:R= ", R.resistance, " Om,C= ", C.capacitance, " F,L= ", L.inductance, " Gn,Gn")
println(" 频率: ", ω/(2π), " 赫兹")
println(" 总阻抗: ", Z_total, " 嗡")
m = Mass(10.0)
k = Spring(1000.0)
c = Damper(50.0)
components_mech = [m, k, c]
Z_mech = total_impedance(components_mech, ω)
println("机械系统(质量-弹簧-阻尼器):")
println(" m = ", m.mass, " 公斤,k= ", k.stiffness, " N/m,c= ", c.damping, " N*s/m")
println(" 机械阻抗: ", Z_mech, " N*s/m")
功能 total_impedance 演示多态性:它接受任何组件(继承人)的向量 AbstractComponent)并总结它们的阻抗。 这是可能的,因为为每种类型定义了一个方法。 impedance. 然后创建特定的组件,并计算其总阻抗为50赫兹的频率。 结果显示在屏幕上,显示了单个方法如何适用于不同的物理域。
9. 与 SystemState 和索引
state_mech = SystemState([0.0; 0.0], 0.0)
println("\机械系统在初始时刻的状态:")
show(state_mech)
println()
state_mech[1] = 0.01
state_mech[2] = 0.5
println("更改后:")
show(state_mech)
println()
系统的状态以时间为0的双元矢量(位移和速度)的形式创建。 由于重新定义 show 输出包含时间戳。 一段时间后 setindex! 您可以通过访问索引来更改状态元素,就像常规数组一样。
10. 多重调度:多态函数 describe_impedance
function describe_impedance(comp::AbstractComponent, ω::Real)
Z = impedance(comp, ω)
println(" ", typeof(comp), " 在ω= ", ω, " 它具有阻抗 ", Z)
end
println("不同类型组件的调度:")
describe_impedance(R, ω)
describe_impedance(C, ω)
describe_impedance(L, ω)
describe_impedance(m, ω)
describe_impedance(k, ω)
describe_impedance(c, ω)
功能 describe_impedance 接受任何元件并输出其阻抗。 为每个传递的对象调用一个专门的方法。 impedance,这是多重调度的本质。 输出显示Julia如何根据参数类型自动选择正确的实现。
结论
示例成功演示:
-AbstractComponent的抽象类型的层次结构
-带where约束的参数化SystemState类型
-内部/外部构造函数与检查
-基本函数的扩展:show,getindex,setindex!,+,*,==,零,一
-使用不同组件的阻抗示例进行多次调度
-方法冲突解决的概念
Julia的多重调度允许您创建灵活且可扩展的系统,其中行为由参数类型的组合决定。 所考虑的电气和机械部件建模示例清楚地显示了这种方法的优点。:
-单界面-功能 impedance 它是为所有组件定义的,但它的实现是特定于每种类型的。
-可扩展性-添加新类型的组件只需要定义其方法 impedance 并且,如果需要,运算符复盖。
-检查的封装-内部构造函数确保只创建物理上正确的对象。
-自然语法-标准函数的重载(getindex, show, +, *)使得使用自定义类型和使用内置类型一样方便。
使用抽象类型和参数结构可以创建反映真实物理关系的层次结构。 与此同时,多重调度成为组织代码的关键机制,确保其清晰度和模块化。 这种方法在需要在单个软件平台内对异构物理域进行建模的工程应用中尤其有用。