# Convergence of SDE solvers

**URL:** <https://discourse.julialang.org/t/convergence-of-sde-solvers/135179>\
**Category:** General Usage\
**Tags:** sde, differentialequation\
**Created:** [January 21, 2026, 8:58am UTC](https://discourse.julialang.org/t/convergence-of-sde-solvers/135179 "2026-01-21T08:58:22Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![diffeqslvr](https://avatars.discourse-cdn.com/v4/letter/d/f475e1/32.png) [@diffeqslvr](https://discourse.julialang.org/u/diffeqslvr)\
**Post date:** [January 21, 2026, 8:58am UTC](https://discourse.julialang.org/t/convergence-of-sde-solvers/135179/1 "2026-01-21T08:58:22Z")

</div>

I am wondering how to make sure that an SDE solver has converged on a trajectory level. In other words, I want to make sure that I have chosen `abstol` and `reltol` small enough.

What I would do in the deterministic case is that I would just solve the differential equation twice for different tolerances. If the results lie on top of each other, I can be sure that I have chosen the tolerances low enough.

In the stochastic case, however, this is not possible. I am new to these types of equations, but as far as I understand, stochastic solvers only have strong convergence, which means that the difference between two trajectories with different step sizes `dt1` and `dt2` vanishes as `~ |dt1 - dt2|^p`.

I find this a bit unintuitive, as I don’t want my solution to depend on my choice of the time step (or the tolerances). To test this, I took an example from the tutorial and executed it with the same seed but different tolerances:

```julia-auto
import StochasticDiffEq as SDE
α = 1
β = 1
u₀ = 1 / 2
f(u, p, t) = α * u
g(u, p, t) = β * u
tspan = (0.0, 1.0)
prob = SDE.SDEProblem(f, g, u₀, tspan)

sol = SDE.solve(prob, SDE.SRIW1(), seed = 123, abstol = 1e-7, reltol = 1e-7, maxiters = Int(1e10), saveat = 0.01)
sol_lower_tol = SDE.solve(prob, SDE.SRIW1(), seed = 123, abstol = 1e-8, reltol = 1e-8, maxiters = Int(1e10), saveat = 0.01)

```

Plotting the two solutions yields

 ![Comparison of the solutions with different tolerances.](https://global.discourse-cdn.com/julialang/original/3X/7/1/715aec964dadc77f0f0009f5e53abf10f7d74dd8.png)

Therefore my questions:

1. Is it better to play with `abstol` and `reltol` or with `dt` and `dtmax` to ensure convergence?
2. How can I be sure that I have chosen the correct solver and that my solver has converged?
3. Is it not a problem that my solution depends on the solver parameters? Or is it just that the stochastics differ (i.e., that different random numbers are chosen) when I modify the tolerances?

---

<div class="post-metadata">

**Author:** ![ChrisRackauckas](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/chrisrackauckas/32/77_2.png) [@ChrisRackauckas](https://discourse.julialang.org/u/ChrisRackauckas)\
**Post date:** [January 21, 2026, 11:38am UTC](https://discourse.julialang.org/t/convergence-of-sde-solvers/135179/2 "2026-01-21T11:38:31Z")

</div>

> [@diffeqslvr](#):
>
> - Is it better to play with `abstol` and `reltol` or with `dt` and `dtmax` to ensure convergence?

Depends on the problem but generally yes.

> [@diffeqslvr](#):
>
> In the stochastic case, however, this is not possible. I am new to these types of equations, but as far as I understand, stochastic solvers only have strong convergence, which means that the difference between two trajectories with different step sizes `dt1` and `dt2` vanishes as `~ |dt1 - dt2|^p`.

I would instead state it as, for a chosen brownian motion, it converges like order p.

> [@diffeqslvr](#):
>
> Plotting the two solutions yields

Those aren’t using the same Brownian motion. You’d want to use things like NoiseWrapper in order to refine the Brownian motion of a different solve.

> **[DiffEqNoiseProcess.NoiseWrapper - Abstract Noise Processes ·...](https://docs.sciml.ai/DiffEqNoiseProcess/stable/abstract_noise_processes/#DiffEqNoiseProcess.NoiseWrapper)**
>
> This produces a new noise process from an old one, which will use its interpolation to generate the noise. This allows you to reuse a previous noise process not just with the same timesteps, but also with new (adaptive) timesteps as well. Thus this...

> [@diffeqslvr](#):
>
> Is it not a problem that my solution depends on the solver parameters? Or is it just that the stochastics differ (i.e., that different random numbers are chosen) when I modify the tolerances?

It’s that the random process is completely different. The random seed only makes sure that your sequence of random numbers is the same. But the Brownian motion is a pairing of dt’s with random numbers, and so if you change the dts you just have a completely different Brownian motion.

---

<div class="post-metadata">

**Author:** ![diffeqslvr](https://avatars.discourse-cdn.com/v4/letter/d/f475e1/32.png) [@diffeqslvr](https://discourse.julialang.org/u/diffeqslvr)\
**Post date:** [January 21, 2026, 12:12pm UTC](https://discourse.julialang.org/t/convergence-of-sde-solvers/135179/3 "2026-01-21T12:12:37Z")

</div>

Thanks for the swift reply. Here is the updated code using DiffEqNoiseProcess:

```julia-auto
import StochasticDiffEq as SDE
using DiffEqNoiseProcess
α = 1
β = 1
u₀ = 1 / 2
f(u, p, t) = α * u
g(u, p, t) = β * u
dt = 1 // 2^4
tspan = (0.0, 1.0)
prob1 = SDE.SDEProblem(f, g, u₀, tspan)

sol = SDE.solve(prob1, SDE.SRIW1(), seed = 123, abstol = 1e-6, reltol = 1e-6, maxiters = Int(1e10), saveat = 0.01, save_noise = true)
W2 = NoiseWrapper(sol.W)
prob2 = SDE.SDEProblem(f, g, u₀, tspan, noise = W2)

sol_lower_tol = SDE.solve(prob2, SDE.SRIW1(), seed = 123, abstol = 1e-7, reltol = 1e-7, maxiters = Int(1e10), saveat = 0.01)

```

Now the curves lie on top of each other:

 ![](https://global.discourse-cdn.com/julialang/original/3X/6/e/6e5a2c2ad8995ff540ccb82c652fc65fe6184aad.png)

So, summing up, one could say the following:  
To make sure that the solver has converged, one can perform the same analysis as in the deterministic case, i.e., solving the dynamics twice for different abstol and reltol. If the curves coincide, than one has chosen a sufficiently low tolerance. However, one must be careful to use the same noise process in both runs.

Is that correct? Or is it ok for the solutions to be different for different tolerances?

---

<div class="post-metadata">

**Author:** ![Oscar\_Smith](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/oscar_smith/32/25343_2.png) [@Oscar\_Smith](https://discourse.julialang.org/u/Oscar_Smith)\
**Post date:** [January 21, 2026, 3:29pm UTC](https://discourse.julialang.org/t/convergence-of-sde-solvers/135179/4 "2026-01-21T15:29:35Z")

</div>

The simpler answer is to trust that the tolerances are working correctly and just set `reltol=1e-6` without bothering to check, but a little paranoia definitely can be a good idea for complex systems like SDEs.

---

<div class="post-metadata">

**Author:** ![ChrisRackauckas](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/chrisrackauckas/32/77_2.png) [@ChrisRackauckas](https://discourse.julialang.org/u/ChrisRackauckas)\
**Post date:** [January 21, 2026, 4:25pm UTC](https://discourse.julialang.org/t/convergence-of-sde-solvers/135179/5 "2026-01-21T16:25:49Z")

</div>

> [@Oscar\_Smith](#):
>
> The simpler answer is to trust that the tolerances are working correctly and just set `reltol=1e-6` without bothering to check, but a little paranoia definitely can be a good idea for complex systems like SDEs.

Because you never have convergence better than order 1.5 in a strong sense, hitting that kind of tolerance is almost impossible with an SDE solver 😅 more like 1e-3 is strict.

> [@diffeqslvr](#):
>
> Is that correct? Or is it ok for the solutions to be different for different tolerances?

That is correct. The tolerances are local strong error, kind of like how the tolerances are local error of the ODE solver.

---

<div class="post-metadata">

**Author:** ![diffeqslvr](https://avatars.discourse-cdn.com/v4/letter/d/f475e1/32.png) [@diffeqslvr](https://discourse.julialang.org/u/diffeqslvr)\
**Post date:** [January 22, 2026, 11:15am UTC](https://discourse.julialang.org/t/convergence-of-sde-solvers/135179/6 "2026-01-22T11:15:39Z")

</div>

I am still trying to get a feeling for SDEs (or maybe I am fundamentally misunderstanding something).

My experience is also that choosing a tolerance of 1e-6 makes the simulation time prohibitively long. However, when I choose a tolerance of 1e-3 - 1e-4, which should be strict, I find the following dynamics for the example script from my previous posts:

 ![](https://global.discourse-cdn.com/julialang/original/3X/d/d/ddb937240ceafe1ba06a0881898cc1afe60f6f57.png)

This looks fairly converged to me, but not quite - or do I have to lower my standards when working with SDEs?

I then did the same analysis for my set of differential equations. I am solving 82 nonlinear equations. Randomness enters through a phase phi which follows a Wiener process. Some of the remaining equations are coupled to the phase like `d/dt u[i] = cos(phi) * u [j] + …` Additionally, there is a `ContinuousCallback`, which applies discontinuous changes to u at random times (but the times also depend on the u vector).

When I now compare the dynamics for tolerances between `1e-3` and `1e-4`, I find that the phase is quite similar:

 ![](https://global.discourse-cdn.com/julialang/original/3X/d/c/dc78d740840556dc47e785f8a625ec22fec9b09e.png)

However, the remaining entries of u look quite different:

 ![](https://global.discourse-cdn.com/julialang/original/3X/8/e/8e70c1bdcc0ac08e6c45300ad7cafd8d0f542061.png)

To me it looks like that due to slight differences of the stochastics the `ContinuousCallback` is activated at a different times (i.e., the first time around `t = 2.8` for the blue curve).

Would you consider this as “converged”?

---

<div class="post-metadata">

**Author:** ![ChrisRackauckas](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/chrisrackauckas/32/77_2.png) [@ChrisRackauckas](https://discourse.julialang.org/u/ChrisRackauckas)\
**Post date:** [January 22, 2026, 11:22am UTC](https://discourse.julialang.org/t/convergence-of-sde-solvers/135179/7 "2026-01-22T11:22:52Z")

</div>

> [@diffeqslvr](#):
>
> This looks fairly converged to me, but not quite - or do I have to lower my standards when working with SDEs?

You really have to lower your standards for SDEs: they converge very slowly.

But also, at that point your error is likely to be dominated by sampling error anyways.

> [@diffeqslvr](#):
>
> `…` Additionally, there is a `ContinuousCallback`, which applies discontinuous changes to u at random times (but the times also depend on the u vector).

That’s a very different model, since an SDE has a discontinuous change at every time point, it’s continuously discontinuous in the derivative (i.e. it has a derivative discontinuity almost everywhere in a measure theoretic sense). Your model has finitely many discontinuities, it’s infinitely less discontinuous.
