Reputation: 21
So I am working on an optimization problem with OpenMDAO. The subject of the optimization problem is to find the best Volume regarding some mission requirements that you chose and then make the Volume converge using 2 ways to compute the weight and compute the delta. So in the end my problem is after doing the code, I run it and then I got an error message saying that : "DerivativesWarning:Constraints or objectives [('obj.obj', inds=[0]), ('c1.c1', inds=[0]), ('c2.c2', inds=[0])] cannot be impacted by the design variables of the problem." and "DerivativesWarning:Design variables [('V', inds=[0]), ('FR', inds=[0])] have no impact on the constraints or objective."
So in my code there is 3 subclass that compute the 2 weight and 1 about aerodynamics values. Then in my optimization class I put my 3 subsystem, I connect my inputs and outputs and then create my objective function and constraints and finally I make the problem work by implementing the optimization and then I try to run it and have my error message So here is the code (it has a lot of formulas but I think that is not really important in my subject I think ):
import openmdao.api as om
from math import *
AR_tails = 1.0
pho_cruise_alt = 0.00211 #in slug/ft3 4000ft
pho_max_alt = 0.001876 #in slug/ft3 8000ft
pho_sea_level = 0.002377 #in slug/ft3
Vs = 84.5 #en f/s
mu = 3.66*10**(-7) #in poise
tc = 0.15 #no unit
nb_lobes = 3 #no unit a voir si on veut mettre ca dasn l'optimisation
range = 1000 #in nautical miles
NE = 4 #number of engine, no unit, a voir par la suite avec la reliaility
BR = 0.7 #no unit
BSFC = 0.48 #Brake Specific fuel consumption, no unit
Payload = 40000 #in lb
FS = 4 #Security factor, no unit
Manufacturing_factor = 1.2 #no unit
nb_septum = 2 #no unit,a voir si je modifie ca pour permettre de le modif
Faf = 1.26 #account for the external attach fitting, no unit
Seaming_factor = 1.06 #no unit
nb_ball = nb_lobes*2 #6 #no unit
Fpsq = 1.0 #account for the fabrication of the tail in lightweight structural material, no unit
surface_CS = 0.2 #percentage of the CS on the tail surface, no unit
Fact = 0.79 #no unit
Finstall = 1.15 #account for the installation of actuators, no unit
lec = 150 #account for the length of control cable for the engines, in ft
Nb_prop = 4 #no unit
Nbl = 3 #no unit
Kp = 31.92 #no unit
nt = 2 #number of fuel tank, no unit
Integral_tank = 0 #percentage of fuel tank that are integral, no unit
Npad = 3 #number of pads for ACLS, no unit
Vsr = 4 #landing sink rate, in f/s
Wcomputer = 250 #account for the weight of the computer, it's estimated, in lb
Wavionics = 250 #account for the weight of instruments, in lb
Wfs = 470 #account for the weight of fuel system, in lb
Kelect = 33.73 #account for a coef that depend on the type of range flight, no unit
np = 0.65 #propeller efficiency, no unit
class Geometry(om.ExplicitComponent):
def setup(self):
self.add_input('V',val=1000000.0)
self.add_input('FR',val=3.0)
self.add_output('V23',val=1.0)
self.add_output('de',val=3.0)
self.add_output('lb',val=1.0)
self.add_output('dc',val=1.0)
self.add_output('w',val=1.0)
self.add_output('ht',val=1.0)
self.add_output('AR',val=1.0)
self.add_output('Swet_body_lobed',val=1.0)
self.add_output('SHT',val=1.0)
self.add_output('SVT',val=1.0)
self.add_output('Wg',val=1.0)
self.add_output('Wh0',val=1.0)
self.add_output('Wh1',val=1.0)
self.add_output('total_fuel',val=1.0)
self.add_output('Woe',val=1.0)
self.add_output('Cd0',val=1.0)
self.add_output('K',val=1.0)
def setup_partials(self):
# Finite difference all partials.
self.declare_partials('*', '*', method='fd')
def compute(self,inputs,outputs):
V = inputs['V']
FR = inputs['FR']
V23 = outputs['V23']
de = outputs['de']
lb = outputs['lb']
dc = outputs['dc']
w = outputs['w']
ht = outputs['ht']
AR = outputs['AR']
Swet_body_lobed = outputs['Swet_body_lobed']
SHT = outputs['SHT']
SVT = outputs['SVT']
Wg= outputs['Wg']
Wh0=outputs['Wh0']
Wh1=outputs['Wh1']
total_fuel=outputs['total_fuel']
Woe = outputs['Woe']
Cd0 = outputs['Cd0']
K = outputs['K']
V23 = (float(V))**(2/3)
de = ((6*V)/(FR*pi))**(1/3) #equivalent diameter of body of revolution
lb = FR*de
dc = (de/(-0.0178*nb_lobes**2+0.361*nb_lobes+0.575))
w = (1+nb_lobes)*(dc/2)
ht = dc
AR = ((4*w**2)/(pi*lb*w))
p = 1.6075
Swet_body_ellipsoid = pi*((lb**p*w**p+lb**p*ht**p+w**p*ht**p)/3)**(1/p)
if nb_lobes == 1:
p_ellipsoid = 2*pi*(dc/2)
p_lobes = 2*pi*(dc/2)
if nb_lobes == 2:
p_ellipsoid = 2.525*pi*(dc/2)
p_lobes = (8*pi*(dc/2))/3
if nb_lobes == 3:
p_ellipsoid = 3.084*pi*(dc/2)
p_lobes = (10*pi*(dc/2))/3
if nb_lobes == 4:
p_ellipsoid = 3.663*pi*(dc/2)
p_lobes = (12*pi*(dc/2))/3
if nb_lobes == 5:
p_ellipsoid = 4.254*pi*(dc/2)
p_lobes = (14*pi*(dc/2))/3
Swet_body_lobed = (p_lobes/p_ellipsoid)*Swet_body_ellipsoid
CHT = -0.0051*(1000000/V)+0.0717
SHT = CHT*(V23*lb)/(0.38*lb)
CVT = -0.0049*(1000000/V)+0.0641
SVT = CVT*(V23*lb)/(0.38*lb)
Cd0tab = []
q = (1/2)*pho_cruise_alt*Vs**2
Re = ((lb*Vs*pho_cruise_alt)/mu)
Cf = (0.455/(log10(Re)**2.58))
FF3D_body = 1+(1.5/FR**1.5)+(7/FR**3)
Cd0_body = ((FF3D_body*Cf*Swet_body_lobed)/V23)
Cd0tab.append(Cd0_body)
FF_tail = 1+1.2*tc+100*tc**4
ctail = ((AR_tails*SHT*0.5)**0.5+(AR_tails*SVT*0.5)**0.5)/2
Re_tail = (ctail*Vs*pho_cruise_alt)/mu
cf_tail = 0.455/(log10(Re_tail)**2.58)
Swet_tail = 2.2*(SHT+SVT)
Cd0_tail = (FF_tail*cf_tail*Swet_tail)/V23
Cd0tab.append(Cd0_tail)
#Add every Cd0 for each part of the airship, check what we choose in the end
Cd0cab = (0.108*Cd0_body*V23+7.7)/V23
Cd0tab.append(Cd0cab)
Cd0eng = NE*4.25/V23
Cd0tab.append(Cd0eng)
Cd0engcooling = NE*(2*10**(-6)*V+4.1)/V23
Cd0tab.append(Cd0engcooling)
Cd0engmount = (0.044*Cd0_body*V23+0.92)/V23
Cd0tab.append(Cd0engmount)
Cd0cables = (9.7*10**(-6)*V+10.22)/V23
Cd0tab.append(Cd0cables)
Cd0ACLS = 0.0002
Cd0tab.append(Cd0ACLS)
Cd0_int = (4.78*10**(-6)*V)/V23
Cd0tab.append(Cd0_int)
Cd0 = sum(Cd0tab)
K = -0.0145*(1/AR)**4 + 0.182*(1/AR)**3 - 0.514*(1/AR)**2 + 0.838*(1/AR) - 0.053
if nb_lobes == 1:
Nl = 2
if nb_lobes == 2:
Nl = 2.25
if nb_lobes == 3:
Nl = 2.4
if nb_lobes == 4:
Nl = 2.5
if nb_lobes == 5:
Nl = 2.54
K = K/Nl
L = 0.0646*V*(pho_max_alt/pho_sea_level)
Wlanding = L/BR
Wh1 = Wlanding - L
A = (326*np)/(BSFC*sqrt(K*Cd0))
B = q*V23*sqrt(Cd0/K)
Wh0 = B*tan((range/A)+atan(Wh1/B))
fuel_burned = Wh0-Wh1
fuelres = fuel_burned*0.05
total_fuel = fuel_burned+fuelres
Wzf = (L/BR)-fuelres
Woe = Wzf - Payload
Wg = Wlanding + fuel_burned
class aerodynamic(om.ExplicitComponent):
def setup(self):
self.add_input('Wh0',val=1.0)
self.add_input('V23',val=1.0)
self.add_input('Cd0',val=1.0)
self.add_input('K',val=1.0)
self.add_output('qmax',val=1.0)
self.add_output('Maxpower_perengine',val=1.0)
self.add_output('Dp',val=1.0)
self.add_output('np_check',val=1.0)
def setup_partials(self):
self.declare_partials('*','*',method='fd')
def compute(self,inputs,outputs):
Wh0= inputs['Wh0']
V23= inputs['V23']
Cd0= inputs['Cd0']
K= inputs['K']
qmax= outputs['qmax']
Maxpower_perengine= outputs['Maxpower_perengine']
Dp= outputs['Dp']
np_check= outputs['np_check']
L_aero = Wh0
qmax = pho_sea_level*(((Vs*1.1)**2)/2)
Clmax_power = (L_aero/qmax/V23)
Drag = (Cd0+K*Clmax_power**2)*qmax*V23
Maxpower_perengine = (Vs*1.1)*(Drag/np/NE/550)
Cs = ((pho_sea_level*Vs**5)/Maxpower_perengine/550/10**2)**(1/5) #10 correspond a une rotation max pour le propeller speed and pho is in slug/ft3
J = 0.156*Cs**2+0.241*Cs+0.138
Dp = ((Vs)/10)/J
np_check = 0.139*Cs**3-0.749*Cs**2+1.37*Cs+0.0115
class second_weight_estimation(om.ExplicitComponent):
def setup(self):
self.add_input('qmax',val=1.0)
self.add_input('ht',val=1.0)
self.add_input('V',val=1000000.0)
self.add_input('SHT',val=1.0)
self.add_input('SVT',val=1.0)
self.add_input('Swet_body_lobed',val=1.0)
self.add_input('dc',val=1.0)
self.add_input('lb',val=1.0)
self.add_input('Maxpower_perengine',val=1.0)
self.add_input('Dp',val=1.0)
self.add_input('total_fuel',val=1.0)
self.add_input('Woe',val=1.0)
self.add_input('Wh0',val=1.0)
self.add_input('Wh1',val=1.0)
self.add_output('Wg2',val=1.0)
def setup_partials(self):
self.declare_partials('*','*',method='fd')
def compute(self,inputs,outputs):
qmax = inputs['qmax']
ht = inputs['ht']
V = inputs['V']
SHT = inputs['SHT']
SVT = inputs['SVT']
Swet_body_lobed = inputs['Swet_body_lobed']
dc = inputs['dc']
lb = inputs['lb']
Maxpower_perengine = inputs['Maxpower_perengine']
Dp = inputs['Dp']
total_fuel = inputs['total_fuel']
Woe = inputs['Woe']
Wh0 = inputs['Wh0']
Wh1 = inputs['Wh1']
Wg2 = outputs['Wg2']
Weight = []
Pint = 1.2*qmax+0.0635*ht
hull_fabric_load = FS*12*(Pint/144)*(dc/2)
hull_fabric_density = 0.0085*hull_fabric_load+1.365
Whull = hull_fabric_density*Manufacturing_factor*Faf*Swet_body_lobed/(16*9) #to convert each unit in yd2 and then go into lb
Septum_fabric_load = 1.5*hull_fabric_load
Septum_fabric_density = 0.0085*Septum_fabric_load+1.365
side_body_area = 0.75 #percentage of envelope side body area
Wseptum = nb_septum*Seaming_factor*Septum_fabric_density*side_body_area*pi*ht*(lb/4)/(16*9) #we have a formula for the body side area after side body area
Wenv = Wseptum+Whull
Weight.append(Wenv)
Vball = V*((1/(pho_max_alt/pho_sea_level))-1)
Sball = nb_ball*pi*(nb_lobes*((Vball/pi)/6))**(2/3)
Wball = 0.035*Sball
Weight.append(Wball)
Wssf = Fpsq*(SHT+SVT)*Faf*(1-surface_CS)
Wcs = Fpsq*(SHT+SVT)*surface_CS
Wtail = Wcs+Wssf
Weight.append(Wtail)
Wact = Fact*Finstall*surface_CS*(SHT+SVT)
Weng = NE*Maxpower_perengine**(0.7956)*4.848
Weight.append(Weng)
Wengmt = 0.64*Weng
Weight.append(Wengmt)
Wec = 60.27*(lec*(NE/100))**(0.724)
Weight.append(Wec)
Wstart = 50.38*((Weng/1000))**(0.459)
Weight.append(Wstart)
Wprop = Nb_prop*Kp*Nbl**(0.391)*(Dp*(Maxpower_perengine/1000))**(0.782)
Weight.append(Wprop)
Wft = 2.49*(total_fuel/6)**(0.6)*nt**(0.2)*NE**(0.13)*(1/(1+Integral_tank))**(0.3) #total fuel is divided by 6 which the weight in lb per gallon for aviation gas
Weight.append(Wft)
Wpress_syst = 0.02*Woe
Weight.append(Wpress_syst)
Ppad = Pint
ACLS_area_landing = 3*(0.23*Wh1*(Vsr/(Npad*Ppad))) #we multiply it by 3 because 3 pads but the configuration can change some stuff and then modify the calculations
ACLS_area_takeoff = Wh0/Ppad
Wacls = 1.6*max(ACLS_area_landing,ACLS_area_takeoff)
Weight.append(Wacls)
Wvms = Wcomputer+Wavionics+Wact
Weight.append(Wvms)
Welect = Kelect * (Wfs+Wcomputer+Wavionics)**(0.51) #maybe don't have this equation
Weight.append(Welect)
Wmsys = 0.05*Woe
Weight.append(Wmsys)
Wuf = 0.01*total_fuel
Weight.append(Wuf)
Wmargin = 0.06*Woe
Weight.append(Wmargin)
Woe2 = sum(Weight)
Wg2 = Woe2 + total_fuel + Payload
class Optimization(om.Group):
def setup(self):
cycle = self.add_subsystem('cycle',om.Group(),promotes_inputs=['V','FR'])
cycle.add_subsystem('d1', Geometry(),promotes_inputs=['V','FR'])
cycle.add_subsystem('d4', aerodynamic())
cycle.add_subsystem('d5', second_weight_estimation(),promotes_inputs=['V'])
cycle.connect('d1.V23','d4.V23')
cycle.connect('d1.lb','d5.lb')
cycle.connect('d1.dc','d5.dc')
cycle.connect('d1.ht','d5.ht')
cycle.connect('d1.Swet_body_lobed','d5.Swet_body_lobed')
cycle.connect('d1.SHT','d5.SHT')
cycle.connect('d1.SVT','d5.SVT')
cycle.connect('d1.Wh0','d4.Wh0')
cycle.connect('d1.Wh0','d5.Wh0')
cycle.connect('d1.Wh1','d5.Wh1')
cycle.connect('d1.total_fuel','d5.total_fuel')
cycle.connect('d1.Woe','d5.Woe')
cycle.connect('d1.Cd0','d4.Cd0')
cycle.connect('d1.K','d4.K')
cycle.connect('d4.qmax','d5.qmax')
cycle.connect('d4.Maxpower_perengine','d5.Maxpower_perengine')
cycle.connect('d4.Dp','d5.Dp')
cycle.nonlinear_solver = om.NonlinearBlockGS()
cycle.set_input_defaults('V',val=2)
self.add_subsystem('obj', om.ExecComp('obj = Wg - Wg2'), promotes= ['obj','Wg','Wg2'])
self.add_subsystem('c1', om.ExecComp('c1 = L/Wg'), promotes=['c1','L','Wg'])
self.add_subsystem('c2', om.ExecComp('c2 = np_check'), promotes=['c2','np_check'])
prob = om.Problem()
prob.model = Optimization()
prob.driver = om.ScipyOptimizeDriver()
prob.driver.options['optimizer'] = 'SLSQP'
# prob.driver.options['maxiter'] = 100
prob.driver.options['tol'] = 1e-8
prob.model.add_design_var('V', lower=0)
prob.model.add_design_var('FR', lower=0)
prob.model.add_objective('obj')
prob.model.add_constraint('c1', lower= 0, upper=0.6)
prob.model.add_constraint('c2', lower=0.95*np, upper=1.05*np)
# Ask OpenMDAO to finite-difference across the model to compute the gradients for the optimizer
prob.model.approx_totals()
prob.setup()
prob.set_val('V',2000000)
prob.set_val('FR',3)
prob.set_solver_print(level=0)
prob.run_driver()
print('minimum found at')
print(prob.get_val('V'))
print(prob.get_val('FR'))
print('minumum objective')
print(prob.get_val('obj'))
The total error message :
C:\Users\umroot\anaconda3\envs\fastuav\lib\site-packages\openmdao\core\total_jac.py:1626: DerivativesWarning:Constraints or objectives [('obj.obj', inds=[0]), ('c1.c1', inds=[0]), ('c2.c2', inds=[0])] cannot be impacted
by the design variables of the problem.
C:\Users\umroot\anaconda3\envs\fastuav\lib\site-packages\openmdao\core\total_jac.py:1655: DerivativesWarning:Design variables [('V', inds=[0]), ('FR', inds=[0])] have no impact on the constraints or objective.
Positive directional derivative for linesearch (Exit mode 8)
Current function value: 0.0
Iterations: 5
Function evaluations: 1
Gradient evaluations: 1
Optimization FAILED.
Positive directional derivative for linesearch
-----------------------------------
minimum found at
[2000000.]
[3.]
minumum objective
[0.]
Thanks in advance for looking at it and hope that we can find the solution together
Upvotes: 0
Views: 124
Reputation: 2704
There are a few issues in the posted code.
When you write
np_check= outputs['np_check']
...
np_check = 5
Python does not write 5 into outputs['np_check']
as you might expect, but instead redefines it as a new integer. As a result, outputs['np_check'] maintains its default value (it's never overridden in the compute method).
To fix this, you can either assign to outputs['np_check']
directly, or force numpy to insert 5 into the array value of np_check using:
np_check[...] = 5
This explicitly tells numpy that you are shoving 5 into all elements of the np_check array (it only contains a single element in your instance).
After this, you'll still get an warning that the constraints and objectives cannot be impacted by your design variables. The best thing to do in this situation is to open up the n-squared diagram that OpenMDAO automatically creates for you in reports/n2.html
of the directory where you executed the script.
In this diagram, variables are listed on the left side. Any variable colored orange is not connected to an output elsewhere in the model. For L
and FR
this makes sense because they are ultimately design variables provided by the optimizer.
But the diagram indicates that L
, Wg
, Wg2
, and np_check
are not connected to outputs. OpenMDAO is assuming that these inputs just hold whatever default value was assigned to them (typically 1.0), but the optimizer is not changing them.
These values appear to be outputs within the cycle
group, but they need to be promoted, first out of their computing components, and then out of the cycle
group itself.
cycle = self.add_subsystem('cycle',om.Group(),
promotes_inputs=['V','FR'],
promotes_outputs=['Wg', 'Wg2', 'L', 'np_check'])
cycle.add_subsystem('d1', Geometry(),
promotes_inputs=['V','FR'],
promotes_outputs=['Wg', 'L'])
cycle.add_subsystem('d4', aerodynamic(),
promotes_outputs=['np_check'])
cycle.add_subsystem('d5', second_weight_estimation(),
promotes_inputs=['V'],
promotes_outputs=['Wg2'])
FYI, I found this by clicking the "circled i" tool in the menubar on the left of the N2 diagram will allow you to see the "promoted" name of variables in the model. Variables which share the same promoted name are implicitly connected in OpenMDAO.
Another thing I did was to remove the nonlinear solver assignment from the cycle
group. This group has no feedback, so the default nonlinear solver is preferred.
Finally, one of the constraints and the objective seemed to have values on the order of 1E6, so I scaled them by setting the ref
value of those responses to 1.0E6. This will cause the optimizer to see the value of 1.0E6
as 1.0
. There's a corresponding ref0
argument to add_objective
and add_constraint
that provides the value which the optimizer sees as zero.
With these changes, I get to a point where the optimizer is forcing the design into a region where log10(Re)
is invalid. You can try raising an OpenMDAO AnalysisError when this occurs, which will force some optimizers to walk back to a valid region, but in this case it seems to insist on driving to that region.
import openmdao.api as om
from math import *
AR_tails = 1.0
pho_cruise_alt = 0.00211 #in slug/ft3 4000ft
pho_max_alt = 0.001876 #in slug/ft3 8000ft
pho_sea_level = 0.002377 #in slug/ft3
Vs = 84.5 #en f/s
mu = 3.66*10**(-7) #in poise
tc = 0.15 #no unit
nb_lobes = 3 #no unit a voir si on veut mettre ca dasn l'optimisation
range = 1000 #in nautical miles
NE = 4 #number of engine, no unit, a voir par la suite avec la reliaility
BR = 0.7 #no unit
BSFC = 0.48 #Brake Specific fuel consumption, no unit
Payload = 40000 #in lb
FS = 4 #Security factor, no unit
Manufacturing_factor = 1.2 #no unit
nb_septum = 2 #no unit,a voir si je modifie ca pour permettre de le modif
Faf = 1.26 #account for the external attach fitting, no unit
Seaming_factor = 1.06 #no unit
nb_ball = nb_lobes*2 #6 #no unit
Fpsq = 1.0 #account for the fabrication of the tail in lightweight structural material, no unit
surface_CS = 0.2 #percentage of the CS on the tail surface, no unit
Fact = 0.79 #no unit
Finstall = 1.15 #account for the installation of actuators, no unit
lec = 150 #account for the length of control cable for the engines, in ft
Nb_prop = 4 #no unit
Nbl = 3 #no unit
Kp = 31.92 #no unit
nt = 2 #number of fuel tank, no unit
Integral_tank = 0 #percentage of fuel tank that are integral, no unit
Npad = 3 #number of pads for ACLS, no unit
Vsr = 4 #landing sink rate, in f/s
Wcomputer = 250 #account for the weight of the computer, it's estimated, in lb
Wavionics = 250 #account for the weight of instruments, in lb
Wfs = 470 #account for the weight of fuel system, in lb
Kelect = 33.73 #account for a coef that depend on the type of range flight, no unit
np = 0.65 #propeller efficiency, no unit
class Geometry(om.ExplicitComponent):
def setup(self):
self.add_input('V',val=1000000.0)
self.add_input('FR',val=3.0)
self.add_output('V23',val=1.0)
self.add_output('de',val=3.0)
self.add_output('lb',val=1.0)
self.add_output('dc',val=1.0)
self.add_output('w',val=1.0)
self.add_output('ht',val=1.0)
self.add_output('AR',val=1.0)
self.add_output('Swet_body_lobed',val=1.0)
self.add_output('SHT',val=1.0)
self.add_output('SVT',val=1.0)
self.add_output('Wg',val=1.0)
self.add_output('Wh0',val=1.0)
self.add_output('Wh1',val=1.0)
self.add_output('total_fuel',val=1.0)
self.add_output('Woe',val=1.0)
self.add_output('Cd0',val=1.0)
self.add_output('K',val=1.0)
self.add_output('L', val=1.0)
def setup_partials(self):
# Finite difference all partials.
self.declare_partials('*', '*', method='fd')
def compute(self,inputs,outputs):
V = inputs['V']
FR = inputs['FR']
outputs['V23'] = V23 = V**(2/3)
outputs['de'] = de = ((6*V)/(FR*pi))**(1/3) #equivalent diameter of body of revolution
outputs['lb'] = lb = FR*de
outputs['dc'] = dc = (de/(-0.0178*nb_lobes**2+0.361*nb_lobes+0.575))
outputs['w'] = w = (1+nb_lobes)*(dc/2)
outputs['ht'] = ht = dc
outputs['AR'] = AR = ((4*w**2)/(pi*lb*w))
p = 1.6075
Swet_body_ellipsoid = pi*((lb**p*w**p+lb**p*ht**p+w**p*ht**p)/3)**(1/p)
if nb_lobes == 1:
p_ellipsoid = 2*pi*(dc/2)
p_lobes = 2*pi*(dc/2)
if nb_lobes == 2:
p_ellipsoid = 2.525*pi*(dc/2)
p_lobes = (8*pi*(dc/2))/3
if nb_lobes == 3:
p_ellipsoid = 3.084*pi*(dc/2)
p_lobes = (10*pi*(dc/2))/3
if nb_lobes == 4:
p_ellipsoid = 3.663*pi*(dc/2)
p_lobes = (12*pi*(dc/2))/3
if nb_lobes == 5:
p_ellipsoid = 4.254*pi*(dc/2)
p_lobes = (14*pi*(dc/2))/3
outputs['Swet_body_lobed'] = Swet_body_lobed = (p_lobes/p_ellipsoid)*Swet_body_ellipsoid
CHT = -0.0051*(1000000/V)+0.0717
outputs['SHT'] = SHT = CHT*(V23*lb)/(0.38*lb)
CVT = -0.0049*(1000000/V)+0.0641
outputs['SVT'] = SVT = CVT*(V23*lb)/(0.38*lb)
Cd0tab = []
q = (1/2)*pho_cruise_alt*Vs**2
Re = ((lb*Vs*pho_cruise_alt)/mu)
try:
Cf = (0.455/(log10(Re)**2.58))
except ValueError as e:
raise om.AnalysisError('Re out of range')
FF3D_body = 1+(1.5/FR**1.5)+(7/FR**3)
Cd0_body = ((FF3D_body*Cf*Swet_body_lobed)/V23)
Cd0tab.append(Cd0_body)
FF_tail = 1+1.2*tc+100*tc**4
ctail = ((AR_tails*SHT*0.5)**0.5+(AR_tails*SVT*0.5)**0.5)/2
Re_tail = (ctail*Vs*pho_cruise_alt)/mu
cf_tail = 0.455/(log10(Re_tail)**2.58)
Swet_tail = 2.2*(SHT+SVT)
Cd0_tail = (FF_tail*cf_tail*Swet_tail)/V23
Cd0tab.append(Cd0_tail)
#Add every Cd0 for each part of the airship, check what we choose in the end
Cd0cab = (0.108*Cd0_body*V23+7.7)/V23
Cd0tab.append(Cd0cab)
Cd0eng = NE*4.25/V23
Cd0tab.append(Cd0eng)
Cd0engcooling = NE*(2*10**(-6)*V+4.1)/V23
Cd0tab.append(Cd0engcooling)
Cd0engmount = (0.044*Cd0_body*V23+0.92)/V23
Cd0tab.append(Cd0engmount)
Cd0cables = (9.7*10**(-6)*V+10.22)/V23
Cd0tab.append(Cd0cables)
Cd0ACLS = 0.0002
Cd0tab.append(Cd0ACLS)
Cd0_int = (4.78*10**(-6)*V)/V23
Cd0tab.append(Cd0_int)
outputs['Cd0'] = Cd0 = sum(Cd0tab)
outputs['K'] = -0.0145*(1/AR)**4 + 0.182*(1/AR)**3 - 0.514*(1/AR)**2 + 0.838*(1/AR) - 0.053
if nb_lobes == 1:
Nl = 2
if nb_lobes == 2:
Nl = 2.25
if nb_lobes == 3:
Nl = 2.4
if nb_lobes == 4:
Nl = 2.5
if nb_lobes == 5:
Nl = 2.54
outputs['K'] = K = outputs['K']/Nl
outputs['L'] = L = 0.0646*V*(pho_max_alt/pho_sea_level)
Wlanding = L/BR
outputs['Wh1'] = Wh1 = Wlanding - L
A = (326*np)/(BSFC*sqrt(K*Cd0))
B = q*V23*sqrt(Cd0/K)
outputs['Wh0'] = Wh0 = B*tan((range/A)+atan(Wh1/B))
fuel_burned = Wh0-Wh1
fuelres = fuel_burned*0.05
outputs['total_fuel'] = fuel_burned+fuelres
Wzf = (L/BR)-fuelres
outputs['Woe'] = Wzf - Payload
outputs['Wg'] = Wlanding + fuel_burned
class aerodynamic(om.ExplicitComponent):
def setup(self):
self.add_input('Wh0',val=1.0)
self.add_input('V23',val=1.0)
self.add_input('Cd0',val=1.0)
self.add_input('K',val=1.0)
self.add_output('qmax',val=1.0)
self.add_output('Maxpower_perengine',val=1.0)
self.add_output('Dp',val=1.0)
self.add_output('np_check',val=1.0)
def setup_partials(self):
self.declare_partials('*','*',method='fd')
def compute(self,inputs,outputs):
Wh0= inputs['Wh0']
V23= inputs['V23']
Cd0= inputs['Cd0']
K= inputs['K']
qmax = outputs['qmax']
Maxpower_perengine = outputs['Maxpower_perengine']
Dp= outputs['Dp']
np_check= outputs['np_check']
L_aero = Wh0
qmax[...] = pho_sea_level*(((Vs*1.1)**2)/2)
Clmax_power = (L_aero/qmax/V23)
Drag = (Cd0+K*Clmax_power**2)*qmax*V23
Maxpower_perengine[...] = (Vs*1.1)*(Drag/np/NE/550)
Cs = ((pho_sea_level*Vs**5)/Maxpower_perengine/550/10**2)**(1/5) #10 correspond a une rotation max pour le propeller speed and pho is in slug/ft3
J = 0.156*Cs**2+0.241*Cs+0.138
Dp[...] = ((Vs)/10)/J
np_check[...] = 0.139*Cs**3-0.749*Cs**2+1.37*Cs+0.0115
class second_weight_estimation(om.ExplicitComponent):
def setup(self):
self.add_input('qmax',val=1.0)
self.add_input('ht',val=1.0)
self.add_input('V',val=1000000.0)
self.add_input('SHT',val=1.0)
self.add_input('SVT',val=1.0)
self.add_input('Swet_body_lobed',val=1.0)
self.add_input('dc',val=1.0)
self.add_input('lb',val=1.0)
self.add_input('Maxpower_perengine',val=1.0)
self.add_input('Dp',val=1.0)
self.add_input('total_fuel',val=1.0)
self.add_input('Woe',val=1.0)
self.add_input('Wh0',val=1.0)
self.add_input('Wh1',val=1.0)
self.add_output('Wg2',val=1.0)
def setup_partials(self):
self.declare_partials('*','*',method='fd')
def compute(self,inputs,outputs):
qmax = inputs['qmax']
ht = inputs['ht']
V = inputs['V']
SHT = inputs['SHT']
SVT = inputs['SVT']
Swet_body_lobed = inputs['Swet_body_lobed']
dc = inputs['dc']
lb = inputs['lb']
Maxpower_perengine = inputs['Maxpower_perengine']
Dp = inputs['Dp']
total_fuel = inputs['total_fuel']
Woe = inputs['Woe']
Wh0 = inputs['Wh0']
Wh1 = inputs['Wh1']
Wg2 = outputs['Wg2']
Weight = []
Pint = 1.2*qmax+0.0635*ht
hull_fabric_load = FS*12*(Pint/144)*(dc/2)
hull_fabric_density = 0.0085*hull_fabric_load+1.365
Whull = hull_fabric_density*Manufacturing_factor*Faf*Swet_body_lobed/(16*9) #to convert each unit in yd2 and then go into lb
Septum_fabric_load = 1.5*hull_fabric_load
Septum_fabric_density = 0.0085*Septum_fabric_load+1.365
side_body_area = 0.75 #percentage of envelope side body area
Wseptum = nb_septum*Seaming_factor*Septum_fabric_density*side_body_area*pi*ht*(lb/4)/(16*9) #we have a formula for the body side area after side body area
Wenv = Wseptum+Whull
Weight.append(Wenv)
Vball = V*((1/(pho_max_alt/pho_sea_level))-1)
Sball = nb_ball*pi*(nb_lobes*((Vball/pi)/6))**(2/3)
Wball = 0.035*Sball
Weight.append(Wball)
Wssf = Fpsq*(SHT+SVT)*Faf*(1-surface_CS)
Wcs = Fpsq*(SHT+SVT)*surface_CS
Wtail = Wcs+Wssf
Weight.append(Wtail)
Wact = Fact*Finstall*surface_CS*(SHT+SVT)
Weng = NE*Maxpower_perengine**(0.7956)*4.848
Weight.append(Weng)
Wengmt = 0.64*Weng
Weight.append(Wengmt)
Wec = 60.27*(lec*(NE/100))**(0.724)
Weight.append(Wec)
Wstart = 50.38*((Weng/1000))**(0.459)
Weight.append(Wstart)
Wprop = Nb_prop*Kp*Nbl**(0.391)*(Dp*(Maxpower_perengine/1000))**(0.782)
Weight.append(Wprop)
Wft = 2.49*(total_fuel/6)**(0.6)*nt**(0.2)*NE**(0.13)*(1/(1+Integral_tank))**(0.3) #total fuel is divided by 6 which the weight in lb per gallon for aviation gas
Weight.append(Wft)
Wpress_syst = 0.02*Woe
Weight.append(Wpress_syst)
Ppad = Pint
ACLS_area_landing = 3*(0.23*Wh1*(Vsr/(Npad*Ppad))) #we multiply it by 3 because 3 pads but the configuration can change some stuff and then modify the calculations
ACLS_area_takeoff = Wh0/Ppad
Wacls = 1.6*max(ACLS_area_landing,ACLS_area_takeoff)
Weight.append(Wacls)
Wvms = Wcomputer+Wavionics+Wact
Weight.append(Wvms)
Welect = Kelect * (Wfs+Wcomputer+Wavionics)**(0.51) #maybe don't have this equation
Weight.append(Welect)
Wmsys = 0.05*Woe
Weight.append(Wmsys)
Wuf = 0.01*total_fuel
Weight.append(Wuf)
Wmargin = 0.06*Woe
Weight.append(Wmargin)
Woe2 = sum(Weight)
Wg2[...] = Woe2 + total_fuel + Payload
class Optimization(om.Group):
def setup(self):
cycle = self.add_subsystem('cycle',om.Group(),
promotes_inputs=['V','FR'],
promotes_outputs=['Wg', 'Wg2', 'L', 'np_check'])
cycle.add_subsystem('d1', Geometry(),
promotes_inputs=['V','FR'],
promotes_outputs=['Wg', 'L'])
cycle.add_subsystem('d4', aerodynamic(),
promotes_outputs=['np_check'])
cycle.add_subsystem('d5', second_weight_estimation(),
promotes_inputs=['V'],
promotes_outputs=['Wg2'])
cycle.connect('d1.V23','d4.V23')
cycle.connect('d1.lb','d5.lb')
cycle.connect('d1.dc','d5.dc')
cycle.connect('d1.ht','d5.ht')
cycle.connect('d1.Swet_body_lobed','d5.Swet_body_lobed')
cycle.connect('d1.SHT','d5.SHT')
cycle.connect('d1.SVT','d5.SVT')
cycle.connect('d1.Wh0','d4.Wh0')
cycle.connect('d1.Wh0','d5.Wh0')
cycle.connect('d1.Wh1','d5.Wh1')
cycle.connect('d1.total_fuel','d5.total_fuel')
cycle.connect('d1.Woe','d5.Woe')
cycle.connect('d1.Cd0','d4.Cd0')
cycle.connect('d1.K','d4.K')
cycle.connect('d4.qmax','d5.qmax')
cycle.connect('d4.Maxpower_perengine','d5.Maxpower_perengine')
cycle.connect('d4.Dp','d5.Dp')
# No feedback, not necessary
# cycle.nonlinear_solver = om.NonlinearBlockGS()
cycle.set_input_defaults('V',val=2)
self.add_subsystem('obj', om.ExecComp('obj = Wg - Wg2'), promotes= ['obj','Wg','Wg2'])
self.add_subsystem('c1', om.ExecComp('c1 = L/Wg'), promotes=['c1','L','Wg'])
self.add_subsystem('c2', om.ExecComp('c2 = np_check'), promotes=['c2','np_check'])
prob = om.Problem()
prob.model = Optimization()
prob.driver = om.ScipyOptimizeDriver()
prob.driver.options['maxiter'] = 100
prob.driver.options['tol'] = 1e-8
prob.model.add_design_var('V', lower=0, ref=1.0E6)
prob.model.add_design_var('FR', lower=0)
prob.model.add_objective('obj', ref=1.0E6)
prob.model.add_constraint('c1', lower=0, upper=1.0)
prob.model.add_constraint('np_check', lower=0.95*np, upper=1.05*np)
# Ask OpenMDAO to finite-difference across the model to compute the gradients for the optimizer
prob.model.approx_totals()
prob.setup()
prob.set_val('V',2000000)
prob.set_val('FR',3)
prob.run_driver()
At this point I think the structure of the OpenMDAO problem is OK (things appear to be connected properly), but something about the optimization problem is not working correctly because of the Reynolds number issue.
Upvotes: 0