Skip to content

Computational performance is significantly worse when using Birkhoff #1084

@TidoHoutepen

Description

@TidoHoutepen

Description

Hi everyone,

I have been experimenting with using the Birkhoff transcription for my optimisation problem, as it might be more robust for my usecase. However, it seems like Birkhoff runs a lot slower for my problem (computing the coloring for my 750 node problem took about 1.5 hours, while for Radau it takes 15 minutes max).

I went back to the basics and compared the performance of Birkhoff to Radau on the Brachistrochrone problem. I used [5,10,20,40] third order segments and [20,40,80,160] nodes for Radau and Birkhoff respectively (as far as I understand this should give them the same resolution). I used IPOPT and gave both transcriptions 100 iterations to solve the problem

The Birkhoff was much slower (see image) and did not converge within the 100 iterations for the three most dense grids.
RadauVSBirkhoff
On top of that the jacobian computations of the Birkhoff optimisation became slower compared to the Radau computations as the amount of points grew:

Full total jacobian for problem 'FirstExample2' was computed 3 times, taking 0.1714210999780334 seconds. --> Birkhoff 20 nodes
Full total jacobian for problem 'FirstExample3' was computed 3 times, taking 0.3252433000016026 seconds. --> Radau 10 segments
Full total jacobian for problem 'FirstExample4' was computed 3 times, taking 0.4679974000318907 seconds. --> Birkhoff 40 nodes
Full total jacobian for problem 'FirstExample5' was computed 3 times, taking 0.5151562999817543 seconds. --> Radau 20 segments
Full total jacobian for problem 'FirstExample6' was computed 3 times, taking 1.0468163999612443 seconds. --> Birkhoff 80 nodes
Full total jacobian for problem 'FirstExample7' was computed 3 times, taking 1.3801417999784462 seconds. --> Radau 40 segments
Full total jacobian for problem 'FirstExample8' was computed 3 times, taking 3.3809573000180535 seconds. --> Birkhoff 160 nodes

As far as I understood Birkhoff should scale better with larger problem sizes, however that does not seem to be case, is there something wrong with my implementation or could this be a bug?

Example

import numpy as np
import openmdao.api as om
import dymos as dm
from dymos.examples.brachistochrone import BrachistochroneODE
import matplotlib.pyplot as plt
import time        

def solveProblem(n_segments:int):

    # Initialize the problem and the optimization driver
    p = om.Problem(model=om.Group())
    p.driver = om.pyOptSparseDriver(optimizer='IPOPT')
    p.driver.declare_coloring()
    p.driver.opt_settings['max_iter'] = 100

    # Create a trajectory and add a phase to it
    traj = p.model.add_subsystem('traj',dm.Trajectory())

    phase = traj.add_phase('phase0', dm.Phase(ode_class=BrachistochroneODE, transcription=dm.Radau(num_segments = n_segments)))

    # Set the state variables
    phase.set_time_options(fix_initial =True, duration_bounds =(0.5,10))
    phase.add_state('y', fix_initial = True, fix_final = True)
    phase.add_state('x', fix_initial = True, fix_final = True)
    phase.add_state('v', fix_initial = True, fix_final = False)

    # Set BC of controls
    phase.add_control('theta', continuity=True, rate_continuity=True, units='deg', lower=0.01, upper=179.9)

    # Set parameters
    phase.add_parameter('g', units='m/s**2', val= 9.80665) 

    # Put in objective function
    phase.add_objective('time', loc='final', scaler=10)

    # Add solver
    #p.model.linear_solver = om.DirectSolver()

    # Setup the problem
    p.setup()

    # Now set the initial and final values 
    p['traj.phase0.t_initial'] = 0.0
    p['traj.phase0.t_duration'] = 2.0

    p.set_val('traj.phase0.states:x', phase.interp('x', ys=[0, 10]))
    p.set_val('traj.phase0.states:y', phase.interp('y', ys=[10, 5]))
    p.set_val('traj.phase0.states:v', phase.interp('v', ys=[0, 0]))
    p.set_val('traj.phase0.controls:theta', phase.interp('theta', ys=[0, 0]))

    dm.run_problem(p)

    print(p.get_val('traj.phase0.timeseries.time')[-1])



def solveProblemBirkhoff(n_nodes:int):

    # Initialize the problem and the optimization driver
    p = om.Problem(model=om.Group())
    p.driver = om.pyOptSparseDriver(optimizer='IPOPT')
    p.driver.declare_coloring()
    p.driver.opt_settings['max_iter'] = 100

    # Create a trajectory and add a phase to it
    traj = p.model.add_subsystem('traj',dm.Trajectory())

    phase = traj.add_phase('phase0', dm.Phase(ode_class=BrachistochroneODE, transcription=dm.Birkhoff(grid=dm.BirkhoffGrid(num_nodes=n_nodes))))

    # Set the state variables
    phase.set_time_options(fix_initial =True, duration_bounds =(0.5,10))
    phase.add_state('y', fix_initial = True, fix_final = True)
    phase.add_state('x', fix_initial = True, fix_final = True)
    phase.add_state('v', fix_initial = True, fix_final = False)

    # Set BC of controls
    phase.add_control('theta', continuity=True, rate_continuity=True, units='deg', lower=0.01, upper=179.9)

    # Set parameters
    phase.add_parameter('g', units='m/s**2', val= 9.80665) 

    # Put in objective function
    phase.add_objective('time', loc='final', scaler=10)

    # Add solver
    #p.model.linear_solver = om.DirectSolver()

    # Setup the problem
    p.setup()

    # Now set the initial and final values 
    p['traj.phase0.t_initial'] = 0.0
    p['traj.phase0.t_duration'] = 2.0

    phase.set_state_val('x',[0,10])
    phase.set_state_val('y',[10,5])
    phase.set_state_val('v',0)
    phase.set_control_val('theta',0.0)

    dm.run_problem(p)

    print(p.get_val('traj.phase0.timeseries.time')[-1])
    


# Lets try these different transcriptions with different number of nodes
n_nodes = [20,40,80,160]
n_segments = [5,10,20,40]

t_birkhoff = []
t_radau = []

for i in range(len(n_nodes)):
    t_start = time.time()
    solveProblem(n_segments[i])
    t_radau.append(time.time()-t_start)
    t_start=time.time()
    solveProblemBirkhoff(n_nodes[i])
    t_birkhoff.append(time.time()-t_start)

plt.plot(n_nodes,t_birkhoff, label='Brikhoff')
plt.plot(n_nodes,t_radau, label='Radau')
plt.xlabel('Number of nodes [-]')
plt.ylabel('Computational time [s]')
plt.legend()
plt.minorticks_on()
plt.grid(which='minor', linestyle='--', alpha=0.4)
plt.grid(which='major', linestyle='-')
plt.tight_layout()
plt.show()

Dymos Version

1.10.1dev0

Relevant environment information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions