Trying to Create a Schmitt Trigger Component in ModelingToolkit.jl

Hello all. I am trying to create a Schmitt trigger component in ModelingToolkit.jl. Specifically the circuit in the image below (source):

I am using the IdealOpAmp component from the MTK standard library, I am assuming it is setup in the same way as the figure below (source):

However I am running into some problems with my code:

using ModelingToolkit
using ModelingToolkitStandardLibrary
using DifferentialEquations
using Plots

const B = ModelingToolkitStandardLibrary.Blocks
const E = ModelingToolkitStandardLibrary.Electrical
@parameters t

@named R1 = E.Resistor(R = 1)
@named R2 = E.Resistor(R = 2)

@named voltage_signal = B.Sine(frequency=1, amplitude=10)
@named source = E.Voltage()

@named op_amp = E.IdealOpAmp()

@named ground = E.Ground()

@named output_pin = E.OnePort()

eqs = [
    connect(voltage_signal.output, source.V)
    connect(source.p, R1.p)
	connect(source.n, ground.g)
    connect(R1.n, op_amp.p1)
	connect(R1.n, R2.p)
    connect(ground.g, op_amp.n1)
	connect(ground.g, op_amp.n2)
	connect(R2.n, output_pin.p)
	connect(op_amp.p2, output_pin.p)
	connect(output_pin.n, ground.g)
]

@named model = ODESystem(
	eqs, t; systems = [R1, R2, voltage_signal, source, op_amp, ground, output_pin]
)

sys = structural_simplify(model)

prob = ODEProblem(sys, [], (0, 10.0), [])

I get the following error when running the ODEProblem function:

ArgumentError: Equations (1) and states (0) are of different lengths. To allow these to differ use kwarg check_length=false.

#check_eqs_u0#150@abstractsystem.jl:1672[inlined]
var"#process_DEProblem#548"(::Bool, ::Nothing, ::Nothing, ::Bool, ::Bool, ::Bool, ::Bool, ::Bool, ::Bool, ::Symbolics.SerialForm, ::Bool, ::Bool, ::Bool, ::Base.Pairs{Symbol, Integer, Tuple{Symbol, Symbol, Symbol}, NamedTuple{(:t, :has_difference, :check_length), Tuple{Int64, Bool, Bool}}}, ::typeof(ModelingToolkit.process_DEProblem), ::Type, ::ModelingToolkit.ODESystem, ::Vector{Any}, ::Vector{Any})@abstractodesystem.jl:621
var"#_#555"(::Nothing, ::Bool, ::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}}, ::Type{SciMLBase.ODEProblem{true, SciMLBase.AutoSpecialize}}, ::ModelingToolkit.ODESystem, ::Vector{Any}, ::Tuple{Int64, Float64}, ::Vector{Any})@abstractodesystem.jl:719
ODEProblem@abstractodesystem.jl:712[inlined]
#_#553@abstractodesystem.jl:699[inlined]
ODEProblem@abstractodesystem.jl:698[inlined]
#ODEProblem#552@abstractodesystem.jl:695[inlined]
ODEProblem@abstractodesystem.jl:694[inlined]
top-level scope@Local: 1[inlined]

Setting the keyword argument check_length=false results in another error when running solve:

Mass matrix size is incompatible with initial condition

sizing. The mass matrix must represent the `vec`

form of the initial condition `u0`, i.e.

`size(mm,1) == size(mm,2) == length(u)`

size(prob.f.mass_matrix,1): 1

length(u0):

Some of the types have been truncated in the stacktrace for improved reading. To emit complete information

in the stack trace, evaluate `TruncatedStacktraces.VERBOSE[] = true` and re-run the code.

get_concrete_u0@solve.jl:1197[inlined]
var"#get_concrete_problem#46"(::Base.Pairs{Symbol, Union{Nothing, Vector{Float64}}, Tuple{Symbol, Symbol}, NamedTuple{(:u0, :p), Tuple{Nothing, Vector{Float64}}}}, ::typeof(DiffEqBase.get_concrete_problem), ::SciMLBase.ODEProblem{Nothing, Tuple{Float64, Float64}, true, Vector{Float64}, SciMLBase.ODEFunction{true, SciMLBase.AutoSpecialize, ModelingToolkit.var"#f#522"{RuntimeGeneratedFunctions.RuntimeGeneratedFunction{(:ˍ₋arg1, :ˍ₋arg2, :t), ModelingToolkit.var"#_RGF_ModTag", ModelingToolkit.var"#_RGF_ModTag", (0xb7b77d1d, 0x7ab1eaa7, 0x99752a83, 0x405ba9ae, 0xa2972979), Expr}, RuntimeGeneratedFunctions.RuntimeGeneratedFunction{(:ˍ₋out, :ˍ₋arg1, :ˍ₋arg2, :t), ModelingToolkit.var"#_RGF_ModTag", ModelingToolkit.var"#_RGF_ModTag", (0x25da9c91, 0x42425f27, 0xecfdcda6, 0x60e772c3, 0xcf06bdb2), Expr}}, Matrix{Float64}, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Vector{Any}, Symbol, Vector{Symbol}, ModelingToolkit.var"#570#generated_observed#530"{Bool, ModelingToolkit.ODESystem, Dict{Any, Any}}, Nothing, ModelingToolkit.ODESystem}, Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}}, SciMLBase.StandardODEProblem}, ::Bool)@solve.jl:1059
get_concrete_problem@solve.jl:1056[inlined]
#solve_up#30@solve.jl:969[inlined]
solve_up@solve.jl:945[inlined]
#solve#28@solve.jl:882[inlined]
solve@solve.jl:872[inlined]
top-level scope@Local: 1[inlined]

I am not an expert with electronic circuits, so I am unsure if my connections are setup correctly. Or let me know if there are other/better ways to model the behaviour of a Schmitt trigger using MTK. Any help with this would be greatly appreciated, thank you!

The errors you are getting both stem from a typo:

	connect(R1.n, R1.p)

should be

	connect(R1.n, R2.p)

But even after that, this ideal op-amp model won’t produce a Schmitt trigger. This model enforces the constraint that there is no current or voltage difference between the inputs, but does not enforce that the sign of the gain is positive. A Schmitt trigger circuit also needs the property that the output sign is the same as the input sign (infinite gain) and the non-ideality that the op-amp has limited output voltage.

You might try connecting a Voltage Sensor to a Voltage Source (with a limiter) via a large gain to produce a more appropriate ideal op-amp for this circuit.

Edit:
A plot after correcting the typo:

soln = solve(prob)
ts=0:0.05:10
plot(soln(ts; idxs=[0, source.v, output_pin.v]))

nogainconstraint

1 Like

MTK might struggle with this system since it does not contain any differential equations, and thus no state variables. You could try connecting an inductive or capacitive load to the output port and see if that works?

I have been pleasantly surprised by how well it deals with this and just solves the (perhaps nonlinear) system and produces a useful answer. But it definitely produces more interesting answers when there are stateful dynamics!