# Force MTK to generate explicit ODEs?

**URL:** https://discourse.julialang.org/t/force-mtk-to-generate-explicit-odes/135245
**Category:** Modelling & Simulations
**Tags:** question, ode, kalman, modelingtoolkit, dae
**Created:** [January 24, 2026, 8:38pm UTC](https://discourse.julialang.org/t/force-mtk-to-generate-explicit-odes/135245 "2026-01-24T20:38:17Z")
**Posts on this page:** 10
**Page:** 1

<div class="post-metadata">

### Author: ![benjamin](https://avatars.discourse-cdn.com/v4/letter/b/3d9bf3/32.png) [@benjamin](https://discourse.julialang.org/u/benjamin)
#### Post date: [January 24, 2026, 8:38pm UTC](https://discourse.julialang.org/t/force-mtk-to-generate-explicit-odes/135245/1 "2026-01-24T20:38:17Z")

</div>

Hi!

I am using ModelingToolkit.jl to model a physical system. The compiler generates an index-1 DAE, which works great for simulation.

However, I need to use a standard Kalman Filter, which requires a minimal state-space representation (\dot{x} = Ax + Bu) with a full-rank observability matrix.

**The Problem:**

When I use `ModelingToolkit.linearize(sys, inputs, outputs)`, the resulting state-space matrices include the dummy (algebraic) states.

- Total states: 5 (3 true states + 2 dummy/algebraic states).
- Observability Matrix Rank: 3.
- Result: The system is unobservable and the Kalman covariance update fails.

**My Question:**

Is there a way to force MTK to perform full substitution/elimination of these algebraic variables to generate a purely explicit ODE system before or during linearization? I essentially need to convert the Mass Matrix DAE into a standard ODE so I can get a minimal A matrix.

---

<div class="post-metadata">

### Author: ![Bart\_van\_de\_Lint](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bart_van_de_lint/32/212161_2.png) [@Bart\_van\_de\_Lint](https://discourse.julialang.org/u/Bart_van_de_Lint)
#### Post date: [January 24, 2026, 8:55pm UTC](https://discourse.julialang.org/t/force-mtk-to-generate-explicit-odes/135245/2 "2026-01-24T20:55:16Z")

</div>

You can use model-order index reduction: [Run only the algebraic part of the solver - #15 by baggepinnen](https://discourse.julialang.org/t/run-only-the-algebraic-part-of-the-solver/128803/15)

---

<div class="post-metadata">

### Author: ![baggepinnen](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/baggepinnen/32/693_2.png) [@baggepinnen](https://discourse.julialang.org/u/baggepinnen)
#### Post date: [January 25, 2026, 6:12am UTC](https://discourse.julialang.org/t/force-mtk-to-generate-explicit-odes/135245/3 "2026-01-25T06:12:40Z")

</div>

See [Home · ControlSystemsMTK Documentation](https://juliacontrol.github.io/ControlSystemsMTK.jl/dev/#Internals:-Transformation-of-non-proper-models-to-proper-statespace-form) for how to get a normal statespace system

---

<div class="post-metadata">

### Author: ![baggepinnen](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/baggepinnen/32/693_2.png) [@baggepinnen](https://discourse.julialang.org/u/baggepinnen)
#### Post date: [January 25, 2026, 6:25am UTC](https://discourse.julialang.org/t/force-mtk-to-generate-explicit-odes/135245/4 "2026-01-25T06:25:56Z")

</div>

See also [GitHub - baggepinnen/LowLevelParticleFiltersMTK.jl: An interface for state estimation using LowLevelParticleFilters on ModelingToolkit models](https://github.com/baggepinnen/LowLevelParticleFiltersMTK.jl) which might be of interest

---

<div class="post-metadata">

### Author: ![benjamin](https://avatars.discourse-cdn.com/v4/letter/b/3d9bf3/32.png) [@benjamin](https://discourse.julialang.org/u/benjamin)
#### Post date: [January 25, 2026, 7:49pm UTC](https://discourse.julialang.org/t/force-mtk-to-generate-explicit-odes/135245/5 "2026-01-25T19:49:35Z")

</div>

Thanks for the link!  
I got it to work, but, interestingly, not with `named_ss()`, though i suspect the issue is `linearize()` which is called by `named_ss`.

For reference, the DAE produced by mtk consists of 3 differential variables (position `s`, current `i`, velocity `v`) and 2 algebraic variables (force `f`, acceleration `a`).

The equations(sys) are:

```julia-auto
 Differential(t)(axis₊prismatic₊s(t)) ~ axis₊prismatic₊v(t)
 Differential(t)(axis₊motor₊inductor₊i(t)) ~ axis₊motor₊inductor₊v(t) / axis₊motor₊inductor₊L
 Differential(t)(axis₊prismatic₊v(t)) ~ axis₊friction₊flange_b₊sˍtt(t) # <- This is an algebraic state
 0 ~ axis₊prismatic₊f(t) + axis₊trans₊flange_trans₊f(t)
 0 ~ axis₊body_mov₊frame_a₊xˍtt(t) - axis₊body_mov₊r1ˍtt(t)

```

**Using `named_ss` produced the same ss model as `linearize` with all 5 states.**

Here is the A matrix produced by linearize:

```julia-auto
5×5 Matrix{Float64}:
  0.0 0.0 1.0 0.0 0.0
 -0.0 -20.0 -251.327 -0.0 -0.0
  0.0 0.0 0.0 0.0 1.0
  0.0 -24.5417 -308.4 0.0 0.0
  0.0 -4.90835 -61.6801 0.0 0.0

```

Notice that the rows 2, 4, 5 (i, f, a) are all linearly dependent.  
That feels like a problem?

I tried bypassing `linearize` by manually building the Descriptor System, using `calculate_massmatrix` and `ForwardDiff.jacobian` on the compiled ODEProblem.

```julia-auto
# 1. Extract Mass Matrix (E) from the working ODEProblem
E = prob.f.mass_matrix

# 2. Compute A numerically using ForwardDiff on the ODE function
f_func = (u) -> (out=similar(u); prob.f(out, u, prob.p, t); out)
A_numeric = ForwardDiff.jacobian(f_func, prob.u0)

# 3. Convert manually
sys_dss = dss(A_numeric, E, B, C, D)
sys_final = dss2ss(sys_dss, fast=false)

```

The A matrix now looks like this:

```julia-auto
5×5 Matrix{Float64}:
  0.0 0.0 1.0 0.0 0.0
 -0.0 -20.0 -251.327 -0.0 -0.0
  0.0 0.0 0.0 0.0 1.0
  0.0 1.25664 0.0 -1.0 -0.120409
  0.0 0.0 0.0 -0.2 1.0

```

**sys\_final now is the correct ss model with 3 states.**

Am i missing something or does `linearize` actually fail to linearize my model correctly?

---

<div class="post-metadata">

### Author: ![baggepinnen](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/baggepinnen/32/693_2.png) [@baggepinnen](https://discourse.julialang.org/u/baggepinnen)
#### Post date: [January 25, 2026, 7:54pm UTC](https://discourse.julialang.org/t/force-mtk-to-generate-explicit-odes/135245/6 "2026-01-25T19:54:16Z")

</div>

Would you mind posting the complete model and your call to linearize, I’ll look into it in the morning?

---

<div class="post-metadata">

### Author: ![benjamin](https://avatars.discourse-cdn.com/v4/letter/b/3d9bf3/32.png) [@benjamin](https://discourse.julialang.org/u/benjamin)
#### Post date: [January 25, 2026, 8:00pm UTC](https://discourse.julialang.org/t/force-mtk-to-generate-explicit-odes/135245/7 "2026-01-25T20:00:26Z")

</div>

Sure, i can do that.  
Might take me some time though, i need to extract the components first so you wont have to deal with a hundred files.

---

<div class="post-metadata">

### Author: ![baggepinnen](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/baggepinnen/32/693_2.png) [@baggepinnen](https://discourse.julialang.org/u/baggepinnen)
#### Post date: [January 25, 2026, 8:04pm UTC](https://discourse.julialang.org/t/force-mtk-to-generate-explicit-odes/135245/8 "2026-01-25T20:04:46Z")

</div>

You can also try calling `minreal` on the system returned by `named_ss` to see if you have gotten a model with pole-zero cancelations

---

<div class="post-metadata">

### Author: ![benjamin](https://avatars.discourse-cdn.com/v4/letter/b/3d9bf3/32.png) [@benjamin](https://discourse.julialang.org/u/benjamin)
#### Post date: [January 29, 2026, 10:56am UTC](https://discourse.julialang.org/t/force-mtk-to-generate-explicit-odes/135245/9 "2026-01-29T10:56:51Z")

</div>

Sorry for the delay.  
I created a test model and a test script, but im not allowed to upload files or post links yet.  
Is there another way to post code?  
But maybe it won’t be necessary, because the issue is probably that I don’t understand `named_ss` properly.

I made a mistake when testing `linearize` a few days back.  
I beliefe linearize works just fine (simulation of the linearized model is correct), but `named_ss` still doesn’t remove the dummy states.

My question now is, what is `named_ss` trying to do?  
I demonstrated in a previous reply that `dss2ss` is capable of producing a reduced state space representation of my model after manually building the descriptor system.  
As i understand it, `named_ss` also uses `dss2ss`, but retrieves the linearized model using `linearize`, which (always?) returns an explicit state space model containing the dummy states, instead of a descriptor system.  
So does `named_ss` try to convert the ss model into descriptor form before calling `dss2ss`?

To answer your last question: `minreal` did work and produced an observable system. 👍

---

<div class="post-metadata">

### Author: ![baggepinnen](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/baggepinnen/32/693_2.png) [@baggepinnen](https://discourse.julialang.org/u/baggepinnen)
#### Post date: [January 29, 2026, 1:30pm UTC](https://discourse.julialang.org/t/force-mtk-to-generate-explicit-odes/135245/10 "2026-01-29T13:30:05Z")

</div>

> [@benjamin](#):
>
> To answer your last question: `minreal` did work and produced an observable system. 👍

Yeah I suspected that would work. `linearize` converts an index 1 DAE into an ODE, but neither `linearize` nor `named_ss` do any reductions, `dss2ss` does. The only thing `named_ss` does over `linearize` is that it can handle situations in which MTK fails to recognize a proper system, producing equations that appear to require the derivative of inputs, `named_ss` performs a transformation to proper form in this case. In addition to this, `named_ss` wraps the result of linearization in a statespace object.

`minreal` computes a minimal realization, that is, it removes modes that are unobservable or uncontrollable, cancelling poles and zeros. `dss2ss` also did this for you. Calling `named_ss |> minreal`, or `named_ss |> sminreal` is thus recommended when you have a DAE. Note, the result is not incorrect with the additional variables, it’s just a larger representation than you need to represent the transfer function from the input to the output.

> [@benjamin](#):
>
> I created a test model and a test script, but im not allowed to upload files or post links yet.  
> Is there another way to post code?

It’s no longer needed now that we know that the issue is 🙂
