Objective function
The objective function in the optimization includes two main implicit enforced constraints (anatomical constraints and tip position) that use the penalty method formulation. Due to the modularity of the framework, adding the new objective term in the optimization is simple and fast. The user would need to modify the component for the objective by adding a new input. Below using the ObjsComp (an OpenMDAO component) demonstrates how to add a new objective term in the objective function.
import numpy as np
from openmdao.api import ExplicitComponent
class ObjsComp(ExplicitComponent):
def initialize(self):
self.options.declare('tube_nbr', default=3, types=int)
self.options.declare('k', default=2, types=int)
self.options.declare('num_nodes', default=3, types=int)
self.options.declare('zeta')
self.options.declare('rho')
self.options.declare('eps_r')
self.options.declare('eps_p')
self.options.declare('lag')
self.options.declare('eps_e')
self.options.declare('norm1')
self.options.declare('norm2')
self.options.declare('norm3')
self.options.declare('norm4')
self.options.declare('norm5')
def setup(self):
num_nodes = self.options['num_nodes']
k = self.options['k']
zeta = self.options['zeta']
#Inputs
# collision avoidance
self.add_input('obj1',shape=(k,1))
# tip position error
self.add_input('targetnorm',shape=(k,1))
# prevent the optimizer not only favoring certain tube for translation
self.add_input('equ_deploylength')
# the norm of the base frame to the origin
self.add_input('locnorm')
self.add_input('rotnorm')
# New user-defined objective can be added here as an input
self.add_input('new_obj')
# outputs
self.add_output('objs')
# partials
self.declare_partials('objs', 'obj1')
self.declare_partials('objs', 'rotnorm')
self.declare_partials('objs', 'targetnorm')
self.declare_partials('objs', 'equ_deploylength')
self.declare_partials('objs', 'locnorm')
# declare the partials of objective function with respect to new objective term
self.declare_partials('objs', 'new_obj')
def compute(self,inputs,outputs):
k = self.options['k']
num_nodes = self.options['num_nodes']
tube_nbr = self.options['tube_nbr']
zeta = self.options['zeta']
rho = self.options['rho']
eps_r = self.options['eps_r']
eps_p = self.options['eps_p']
lag = self.options['lag']
eps_e = self.options['eps_e']
norm1 = self.options['norm1']
norm2 = self.options['norm2']
norm3 = self.options['norm3']
norm4 = self.options['norm4']
norm5 = self.options['norm5']
obj1 = inputs['obj1']
equ_deploylength = inputs['equ_deploylength']
locnorm = inputs['locnorm']
rotnorm = inputs['rotnorm']
targetnorm = inputs['targetnorm']
new_obj = inputs['new_obj']
magnitude = np.sum(zeta * obj1 / norm1)\
+ eps_e * equ_deploylength / norm2 \
+ np.sum(0.5 * rho * targetnorm**2 / (norm3**2)) \
+ np.sum(lag * targetnorm/(norm3)) \
+ eps_p * locnorm/(norm4) \
+ eps_r * rotnorm/(norm5) \
+ new_obj # new term can be added here
outputs['objs'] = magnitude.squeeze()
def compute_partials(self,inputs,partials):
""" partials Jacobian of partial derivatives."""
k = self.options['k']
num_nodes = self.options['num_nodes']
tube_nbr = self.options['tube_nbr']
zeta = self.options['zeta']
rho = self.options['rho']
eps_e = self.options['eps_e']
eps_r = self.options['eps_r']
eps_p = self.options['eps_p']
norm1 = self.options['norm1']
norm2 = self.options['norm2']
norm3 = self.options['norm3']
norm4 = self.options['norm4']
norm5 = self.options['norm5']
lag = self.options['lag']
targetnorm = inputs['targetnorm']
partials['objs','obj1'][:] = (zeta/norm1).T
partials['objs','targetnorm'][:] = (rho*targetnorm/(norm3**2) + lag/(norm3)).T
partials['objs','equ_deploylength'][:] = eps_e/ norm2
partials['objs','locnorm'][:] = eps_p/norm4
partials['objs','rotnorm'][:] = eps_r/norm5
# compute the partials and give the model analytically
partials['objs','new_obj'][:] = 1