Lattice systems

| Home | Libraries | People | FAQ | More

### Lattice systems

odeint can also be used to solve ordinary differential equations defined on lattices. A prominent example is the Fermi-Pasta-Ulam system [8] . It is a Hamiltonian system of nonlinear coupled harmonic oscillators. The Hamiltonian is

H = Σ​i p​i2/2 + 1/2 ( q​i+1 - q​i )^2 + β / 4 ( q​i+1 - q​i )^4

Remarkably, the Fermi-Pasta-Ulam system was the first numerical experiment to be implemented on a computer. It was studied at Los Alamos in 1953 on one of the first computers (a MANIAC I) and it triggered a whole new tree of mathematical and physical science.

Like the Solar System, the FPU is solved again by a symplectic solver, but in this case we can speed up the computation because the q components trivially reduce to dq​i / dt = p​i. odeint is capable of doing this performance improvement. All you have to do is to call the symplectic solver with an state function for the p components. Here is how this function looks like

    typedef vector< double > container_type;

    struct fpu
    {
        const double m_beta;

        fpu( const double beta = 1.0 ) : m_beta( beta ) { }

        // system function defining the ODE
        void operator()( const container_type &q , container_type &dpdt ) const
        {
            size_t n = q.size();
            double tmp = q[0] - 0.0;
            double tmp2 = tmp + m_beta * tmp * tmp * tmp;
            dpdt[0] = -tmp2;
            for( size_t i=0 ; i<n-1 ; ++i )
            {
                tmp = q[i+1] - q[i];
                tmp2 = tmp + m_beta * tmp * tmp * tmp;
                dpdt[i] += tmp2;
                dpdt[i+1] = -tmp2;
            }
            tmp = - q[n-1];
            tmp2 = tmp + m_beta * tmp * tmp * tmp;
            dpdt[n-1] += tmp2;
        }

        // calculates the energy of the system
        double energy( const container_type &q , const container_type &p ) const
        {
            // ...
        }

        // calculates the local energy of the system
        void local_energy( const container_type &q , const container_type &p , container_type &e ) const
        {
            // ...
        }
    };

You can also use `boost::array< double , N >` for the state type.

Now, you have to define your initial values and perform the integration:

    const size_t n = 64;
    container_type q( n , 0.0 ) , p( n , 0.0 );

    for( size_t i=0 ; i<n ; ++i )
    {
        p[i] = 0.0;
        q[i] = 32.0 * sin( double( i + 1 ) / double( n + 1 ) * M_PI );
    }

    const double dt = 0.1;

    typedef symplectic_rkn_sb3a_mclachlan< container_type > stepper_type;
    fpu fpu_instance( 8.0 );

    integrate_const( stepper_type() , fpu_instance ,
            make_pair( boost::ref( q ) , boost::ref( p ) ) ,
            0.0 , 1000.0 , dt , streaming_observer( cout , fpu_instance , 10 ) );

The observer uses a reference to the system object to calculate the local energies:

    struct streaming_observer
    {
        std::ostream& m_out;
        const fpu &m_fpu;
        size_t m_write_every;
        size_t m_count;

        streaming_observer( std::ostream &out , const fpu &f , size_t write_every = 100 )
        : m_out( out ) , m_fpu( f ) , m_write_every( write_every ) , m_count( 0 ) { }

        template< class State >
        void operator()( const State &x , double t )
        {
            if( ( m_count % m_write_every ) == 0 )
            {
                container_type &q = x.first;
                container_type &p = x.second;
                container_type energy( q.size() );
                m_fpu.local_energy( q , p , energy );
                for( size_t i=0 ; i<q.size() ; ++i )
                {
                    m_out << t << "\t" << i << "\t" << q[i] << "\t" << p[i] << "\t" << energy[i] << "\n";
                }
                m_out << "\n";
                clog << t << "\t" << accumulate( energy.begin() , energy.end() , 0.0 ) << "\n";
            }
            ++m_count;
        }
    };

The full cpp file for this FPU example can be found here fpu.cpp