-
Notifications
You must be signed in to change notification settings - Fork 68
Description
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.

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