I got rid of the explicit parameter method and replaced it by ParametrizedInterpolation:
@mtkmodel HeatingDiode begin
begin
k = 1.380649e-23 # Boltzmann constant (J/K)
q = 1.602176634e-19 # Elementary charge (C)
end
@extend v, i = oneport = OnePort(; v = 0.0)
@components begin
port = HeatPort()
end
@parameters begin
Is = 1e-6, [description = "Saturation current (A)"]
n = 1, [description = "Ideality factor"]
end
@variables begin
Vt(t), [description = "Thermal voltage"]
end
@equations begin
Vt ~ k * port.T / q # Thermal voltage equation
i ~ Is * (exp(v / (n * Vt)) - 1) # Shockley diode equation
port.Q_flow ~ -v * i # -LossPower
end
end
function SingleDiode(data, time)
@named I_L = ParametrizedInterpolation(LinearInterpolation, data, time)
@mtkmodel Single_Diode begin
@parameters begin
n::Float64 = 1.0, [description = "Ideality factor"]
I_s::Float64 = 1e-6, [description = "Saturation current (A)"]
R_sh_val::Float64 = 1e5, [description = "Shunt resistance (Ohm)"]
R_s_val::Float64 = 1e-1, [description = "Series resistance (Ohm)"]
end
@components begin
R_sh = Resistor(R = R_sh_val)
R_s = Resistor(R = R_s_val)
diode = HeatingDiode(n = n, Is = I_s)
source = Current()
temp = FixedTemperature(T = 300.0)
pos = Pin()
neg = Pin()
end
@equations begin
# positive side
connect(I_L.output, source.I)
connect(source.n, diode.p)
connect(source.n, R_sh.p)
connect(source.n, R_s.p)
connect(R_s.n, pos)
# negative side
connect(source.p, diode.n)
connect(source.p, R_sh.n)
connect(source.p, neg)
# temperature
connect(temp.port, diode.port)
end
end
@mtkbuild sys = Single_Diode()
return sys
end
Which gives:
ExtraVariablesSystemException: The system is unbalanced. There are 35 highest order derivative variables and 34 equations.
More variables than equations, here are the potential extra variable(s):
pos₊v(t)
Note that the process of determining extra variables is a best-effort heuristic. The true extra variables are dependent on the model and may not be in this list.
I haven’t been able to figure out what’s missing yet. Any suggestions are welcome
Regarding your root comment using the explicit interpolation parameter:
Could you give an example for how you’re creating the model? The code snippet only includes the model definition. In addition, I would recommend specifying a more concrete type than Function for type stability. If you want to swap out the function after building the model, you could go the FunctionWrapper route and declare it as I_L(::Float64)::Float64.
As for the second approach using ParametrizedInterpolation:
I got rid of the explicit parameter method and replaced it by ParametrizedInterpolation:
There are two problems with this model:
ParametrizedInterpolation is created outside the @mtkmodel and used inside. This doesn’t include it as a subcomponent. It seems that the way ParametrizedInterpolation is defined prevents it from being used inside @components, which is something the standard library should probably fix. In the meantime, the last few lines of function SingleDiode should be:
The second problem is the reason your system is unbalanced. The ParametrizedInterpolation needs an input, using which it samples the data. It does not sample with respect to time by default. If you wish to sample with respect to time, you can add I_L.input.u ~ t to the equations.
An additional performance enhancement for this approach:
Judging from the root comment, it seems you do not need the interpolation data as a parameter. In such a case, you can replace ParametrizedInterpolation with Interpolation, which simply stores the interpolant as a callable symbolic.
Thank you very much for your detailed comments, it’s working now. I put the code at the bottom of this comment for reference, if anyone is reading in the future.
The main advantage of this block over the Interpolation one is that one can use it for optimization problems. Currently, this supports forward mode AD via ForwardDiff, but due to the increased flexibility of the types in the component, this is not as fast as the Interpolation block, so it is recommended to use only when the added flexibility is required.
I definitely want to support AD. That’s one of the main goals of the package I am trying to build (A PV simulator that supports AD). So I think I should stick to ParametrizedInterpolation for now?
Code for reference:
function SingleDiode(data, time)
@named I_L = ParametrizedInterpolation(LinearInterpolation, data, time)
@mtkmodel Single_Diode begin
@parameters begin
n::Float64 = 1.0, [description = "Ideality factor"]
I_s::Float64 = 1e-6, [description = "Saturation current (A)"]
R_sh_val::Float64 = 1e5, [description = "Shunt resistance (Ohm)"]
R_s_val::Float64 = 1e-2, [description = "Series resistance (Ohm)"]
end
@components begin
R_sh = Resistor(R = R_sh_val)
R_s = Resistor(R = R_s_val)
diode = HeatingDiode(n = n, Is = I_s)
source = Current()
temp = FixedTemperature(T = 300.0)
pos = Pin()
neg = Pin()
end
@equations begin
# current from irradiance
I_L.input.u ~ t
connect(I_L.output, source.I)
# positive side
connect(source.n, diode.p)
connect(source.n, R_sh.p)
connect(source.n, R_s.p)
connect(R_s.n, pos)
# negative side
connect(source.p, diode.n)
connect(source.p, R_sh.n)
connect(source.p, neg)
# temperature
connect(temp.port, diode.port)
end
end
@named sys = Single_Diode()
sys = compose(sys, [I_L])
structural_simplify(sys)
end
As a side note, is there a way to bring (data, time) directly into the initialization of this block? Then I could get rid of the function SingleDiode(), which is just a wrapper.