0

I want to solve for the time taken to return to the initial condition. As a toy example, let's take a simple harmonic oscillator d/dt (x, y) = (y, -x), with initial condition (x,y) = (1,0).

I want the code to return T = 6.28... (= 2π), since this is the time taken to return to the initial condition with y positive. It should then stop integration when it reaches this point.

import numpy as np
from scipy.integrate import solve_ivp

def f(t,x):
    return [x[1], -x[0]]

def event(t,x):
    return x[0]

event.direction = 1
event.terminal = True # Try false, this gives the result but integrates beyond the event I need.

initial_condition = [0, 1]

sol = solve_ivp(f, [0, 10], initial_condition, method='RK45',dense_output=True, \
                events = event, rtol=1e-6, atol=3e-6)

print(sol.t_events)

The trouble is setting event.terminal = True boots the integration straight away since the event is satisfied at t = 0. Is there any way to have the integration stopping only once the event has been satisfied twice?

Sam Spedding
  • 121
  • 6
  • Have you tried modifying `event` to e.g. exclude `t=0` via a simple `if` statement? – Peter Meisrimel Aug 17 '20 at 16:21
  • Yes, well I've tried `if t<0.1: return 1` but it doesn't work. The event has to be a continuous function. I've also tried a workaround where you add `1-10t` if `t<0.1` and this sometimes works, but only if the trajectory is increasing at the origin sufficiently quickly(?), and gives an incorrect value otherwise. – Sam Spedding Aug 17 '20 at 16:28

2 Answers2

1

For future reference here is the workaround I've been using:

Set the initial condition to be a tiny amount in front of the event, e.g. initial_condition = [1e-100, 1] rather than [0, 1]. It doesn't seem to affect the trajectory, even for higher degree-of-freedom chaotic systems, and yet it doesn't trigger the event at t=0.

Another workaround that would work would be to integrate without an event for some period of time and then use the result as initial conditions for further integration, this time with the event in place. I doubt this answer will be useful for anyone but I've posted this just for completeness.

Sam Spedding
  • 121
  • 6
1

Similar to your workaround, I add 1e-100 to the event instead of to the initial state. This has the benefit of not affecting the trajectory at all, not even theoretically.

Of course it does mean that your future events will happen a tiny fraction earlier, but it depends on your situation if that matters to you.

For the example code you wrote, that would make your event the following:

def event(t,x):
    return x[0] + 1e-100

One thing I've tried that doesn't work is to modify the termination parameter during the integration. I thought if I start with event.terminal = False but then inside the event definition set event.terminal = True, then it would continue during the first event (at t=0), and terminate at the second event. However it seems to ignore the changed parameter if it's changed during the integration.

(This question is related to Solve_ivp integration gets stuck if initial condition triggers event with terminal = True , but I don't have the reputation to flag anything.)

Mqrius
  • 11
  • 1
  • Instead of 1e-100, consider using `sys.float_info.min`, which will be guaranteed to be the smallest value possible. – blackbrandt Aug 02 '21 at 16:51
  • Instead of 1e-100, consider using `sys.float_info.min`, which will be guaranteed to be the smallest value possible. – blackbrandt Aug 02 '21 at 16:52