SHAPE OPTIMISATION OF UAV WING

Christopher Iliffe Sprague


ABSTRACT

This paper examines the design of a wing for a new long-endurance aircraft that will be used as a telecommunications platform. In particular, the problem of optimizing the shape of the wing’s spar, acting as the aircraft’s main structural support, in order to minimize its mass, is analyzed and solved. In doing so, a robust object-orientated programming approach is taken to transcribe the problem and flexibly find optimal solutions from user defined design guesses. Using sequential quadratic programming, several numerical differentiation paradigms are investigated in relation to the problem’s objective and constraints. It is shown that an optimal spar mass, over 60% lower than the nominal, is consistently achieved while satisfying all constraints.

Index TermsWing Structure, Shape Optimization

Contents

1 Problem Model
 1.1 Specifications
 1.2 Problem Formulation
 1.3 Geometrical Parametrization
  1.3.1 Design Vector
  1.3.2 Spanwise Location
  1.3.3 Volume and Mass
 1.4 Material Stress Constraints
  1.4.1 Load Distribution
  1.4.2 Second Moment of Area
  1.4.3 Displacement and Stress
 1.5 Manufacturing Constraints
  1.5.1 Radii Bounds
  1.5.2 Thickness
2 Optimization
 2.1 Sequential Quadratic Programming
 2.2 Objective and Constraint Gradients
  2.2.1 Forward Difference
  2.2.2 Central Difference
  2.2.3 Complex Step
3 Results
4 Conclusions
5 Appendix
 5.1 Optimization Outputs
 5.2 Optimization Figures
 5.3 Source Code

List of Tables

Problem Specifications

List of Figures

Spanwise Force Distribution.
Forward Difference with 20 Nodes
Geometry
Vertical Displacement
Stress
Central Difference with 20 Nodes
Geometry
Vertical Displacement
Stress
Complex Difference with 20 Nodes
Geometry
Vertical Displacement
Stress
Objective Convergence with 20 Nodes
Forward Difference
Central Difference
Complex Difference

Listings

1 Problem Model
 1.1 Specifications
 1.2 Problem Formulation
 1.3 Geometrical Parametrization
  1.3.1 Design Vector
  1.3.2 Spanwise Location
  1.3.3 Volume and Mass
 1.4 Material Stress Constraints
  1.4.1 Load Distribution
  1.4.2 Second Moment of Area
  1.4.3 Displacement and Stress
 1.5 Manufacturing Constraints
  1.5.1 Radii Bounds
  1.5.2 Thickness
2 Optimization
 2.1 Sequential Quadratic Programming
 2.2 Objective and Constraint Gradients
  2.2.1 Forward Difference
  2.2.2 Central Difference
  2.2.3 Complex Step
3 Results
4 Conclusions
5 Appendix
 5.1 Optimization Outputs
 5.2 Optimization Figures
 5.3 Source Code

1.

1 Problem Model

1.1.

1.1 Specifications

Preceding the construction of the optimization problem, it is necessary to define the constant metrics that characterize the nature of the problem. For the case of an annular spar, the problem parametrization requires several design metrics to be taken into account, namely: wing semi-span L, material density ρ, Young’s modulus E, ultimate tensile/compressive strength σmax, aircraft operational mass M, minimum spar thickness Tmin, minimum inner wall radius rmin, and maximum outer wall radius Rmax.

Additionally, the operational conditions that the aircraft will be subjected to need to be taken into account, namely: gravitational acceleration g, and maximum operational G-force G. In this particular investigation, attention will be directed towards the case of a carbon fiber spar, whose specifications are enumerated in Table 1. One can instantiate a UAV of this nature through an object-oriented approach, as shown in Listing 1. The full source code of the UAV class is shown in Listing 8.


Listing 1: UAV Instantiation
% Instantiate the UAV 
Raptor = UAV(... % With properties: 
  7.5,       ... % Wing semi-span [m]. 
  1600,      ... % Spar density [kg/m^3]. 
  7e10,      ... % Modulus of elasticity [Pa]. 
  600e6,     ... % Maximum spar strength [Pa]. 
  500,       ... % Mass of aircraft [Kg]. 
  9.807,     ... % Earth's gravity [m/s^2]. 
  2.5e-3,    ... % Minimum spar thickness [m]. 
  1e-2,      ... % Minimum inner spar radius [m]. 
  5e-2,      ... % Maximum outer spar radius [m]. 
  2.5        ... % Maximum operational G-force. 
  );


Table 1: Problem Specifications



Parameter SymbolValue



Semi-Span L 7.5 m



Density ρ 1600 kg∕m3



Young’s Modulus E 70 GPa



Ultimate Strength σmax 600 MPa



Aircraft Mass M 500 kg



Minimum Thickness Tmin 2.5 mm



Minimum Inner Radius rmin 1 cm



Maximum Outer RadiusRmax 5 cm



Gravity g 9.81 m∕s2



G-Force G 2.5




1.2.

1.2 Problem Formulation

The objective of this optimization problem is to minimize the total mass of the spar while not allowing its structural stress σ at any spanwise location to exceed its material’s ultimate strength σmax during its maximal operational loading conditions. Additionally, there are also manufacturing constraints that dictate the spar’s inner radius r and outer radius R to satisfy r rmin and R Rmax, while satisfying a thickness constraints R - r Tmin. In a more formal manner, this problem’s formulation is shown as follow:

m inimize m (R ) | R ↦→ {r(x),R(x)} | x ∈ [0,L]
   R
subje∀cxtto rmin ≤ r(x ) ≤ rmin + Tmin

         Rmax - Tmin ≤ R(x) ≤ Rmax
         r(x)- R (x ) ≤ - Tmin
         σ (x) - σmax ≤ 0
Essentially, the annular spar’s inner and outer spanwise radii {r(x),R(x)} must be manipulated until the spar’s total mass m(R) has reached a minimum value for all possible satisfactory forms of r(x) and R(x).

1.3.

1.3 Geometrical Parametrization

Because finite element methods will be used to solve this optimization problem, it is required to discretize the spar’s geometry into a series of spanwise finite elements. Herein, the wing’s spar will be represented by a series of nodes, indexed by i, where i = 1 and i = n signify the locations of the wing’s root and tip respectively. As such, it can be deduced that there are n nodes and n- 1 finite elements along the span of the spar. Hence, the set of node indexes N and finite element indexes E are defined as follows

  N = [i,i+ 1,...,n]

E = [j,j + 1,...,n- 1]

1.3.1.

1.3.1 Design Vector

It is appropriate to begin the optimization process with the formulation of the design vector R, composed of inner radii r and outer radii R. Abiding to the form of a column vector, it is necessary to apply a change of variables and define the design vector as follows,

  R = [ξk,ξk+1,...,ξ2n]T

{ξ2k-1,ξ2k} ← {ri,Ri}∀i ∈ I
It can be seen that the inner and outer radii are sequenced to occur for every other index in the design vector R.

1.3.2.

1.3.2 Spanwise Location

Having deduced the number of nodes n from the user supplied design vector, the spanwise location along the spar can be discretized in a similar manner as,

x = [xi,xi+1,...,xn] | x1 = 0,xn = L
, where it is noted that the first and final index values of xi satisfy the length of the wing’s semi-span.

1.3.3.

1.3.3 Volume and Mass

At each node i along the wing’s span, the spar’s cross section is represented as a circular annulus of inner and outer radii, ri and Ri, respectively, whose area is defined by Ai = π(Ri2 - ri2). Assuming this cross sectional area characterization is consistent across all nodes, the inner and outer surfaces of each finite element j are linearly interpolated between their corresponding nodes i = j and i = j + 1. Hence the nodes’ cross sectional areas are integrated along the interpolations, yielding the volume of each finite element as follows

Vj = π-(r2j- R2j+rjrj+1- RjRj+1+r2j+1- R2j+1)(xj- xj+1)
     3
As such, the total volume of the spar can be computed by a summation across all finite elements V = j=1n-1V j. Subsequently the spar’s total mass is obtained as m = ρV , and hence the optimization problem’s objective function m(R).

1.4.

1.4 Material Stress Constraints

As formulated in Section 1.2, the structural stress at any spanwise location along the spar must not exceed its material’s ultimate strength, σi - σmax 0 i I. Modeling the wing’s spar as a cantilever beam, Euler-Bernoulli beam theory can be exploited to compute the spar’s nodal stress.

1.4.1.

1.4.1 Load Distribution

In this study, it is assumed that the force distribution in the spanwise direction has an approximately linear distribution, with maximum load at the root and zero load at the tip. Hence the force at each node can be formulated as follows

     GM g (    x)
Fi = ----- 1-  -i
      L        L
With the specifications given in Table 1 and Listing 1, the nature of the spar’s force distribution can be seen in Figure 1.



Fig. 1: Spanwise Force Distribution.

PIC


1.4.2.
1.4.2 Second Moment of Area

In order to calculate the displacement of the spar at any location along its span, the second moment of inertia at each node must first be calculated. For a circular annulus describing the cross section of any node along the spar’s span, the second moment of area is formulated as such

Ii = π-(R4i - r4i)
    4

1.4.3.

1.4.3 Displacement and Stress

Assuming a transversely applied load distribution and cross sectional symmetry, a cubic Hermite finite-element basis is used to solve the Euler-Bernoulli beam equations. The vertical and angular displacement of the spar at each node along the beam are calculated, using the aforementioned formulations of the load distribution and second moment of area. The beam’s vertical displacement is formulated as follows δ = [δii+1,n]T. Using the spar’s displacements, its nodal tensile stresses are computed and formulated as such σ = [σii+1,n]T. Hence the constraint function must satisfy the following

σi - σmax ≤ 0 ∀i ∈ I
Because the derivation of this method is beyond the scope of this paper, the reader is advised to consult [?] for further information.

1.5.

1.5 Manufacturing Constraints

As required by the optimization problem’s specifications, the inner and outer radii of each annular cross section cannot be less than Tmin apart, the inner radius cannot be smaller than rmin, and the outer radius cannot be larger than Rmax.

1.5.1.

1.5.1 Radii Bounds

In formulating the range to which the inner radii ri and outer radii Ri are bounded, it is necessary to formally define lower and upper bound vectors L and U, respectively, that satisfy L R U, corresponding to the form of the transformed design vector R described in Section 1.3.1. The lower and upper bounds for the radii, corresponding to the constraints described in Section 1.2, are formulated as such

L = [λk,...,λ2n]T | {λ2k-1,λ2k} ← {rmin,rmin + Tmin}
              T
U = [υk,...,υ2n]  | {υ2k-1,υ2k} ← {Rmax - Tmin,Rmax}

1.5.2.

1.5.2 Thickness

In order to enforce the constraint that the annular spar’s wall thickness may not be smaller than Tmin and that the outer radii must always be greater than the inner radii Ri > ri i I, linear inequality constraints must be instantiated. In doing so, it is required to formulate a n × 2n matrix A and n × 1 column vector b that satisfy AR b. The matrices are defined as follows

    ⌊ α    ⋅⋅⋅  α   ⌋
    |  i.,k  .     i,2.n|
A = ⌈  ..    ..    .. ⌉ | {αi,2i-1,αi,2i} ← {1,- 1}
      αn,k  ⋅⋅⋅  αn,2n
         b = [β ,...,β ]T | β ← - T
              i     n     i     min
where R is defined as the 2n × 1 column vector described in Section 1.3.1. For further information on formatting constraints and objectives to accommodate Matlab’s built in functions, the reader is advised to consult [?].

2.

2 Optimization

2.1.

2.1 Sequential Quadratic Programming

Sequential quadratic programming (SQP) has proven itself as one of the most successful methods for solving nonlinearly constrained optimization problems. Essentially, SQP aims to model the nonlinear programming problem (NLP) at a given approximate solution Rh by a quadratic programming subproblem, then use the subproblem to construct a better approximation Rh+1. This process is iterated until convergence to a solution R* is achieved. SQP is not a feasible point method; neither the point nor any of the subsequent iterates are required to be feasible. However, the SQP method does satisfy the bounds L and U at every iteration, and is ideal for small to medium scale problem such as in the case of this paper’s optimization problem, where optimality returns are generally diminished for n > 40. This method of nonlinear constrained optimization is handled natively by Matlab’s fmincon function [?], and described in more detail in [?].

2.2.

2.2 Objective and Constraint Gradients

In order to achieve an optimal design vector R* that satisfies m(R*) m(R)R↦→{ri,Ri}∀i I, both the necessary and sufficient conditions for optimality need to be realized. It is necessary for an optima at a stationary point R to have a first derivative of zero R = 0. It is sufficient to call the stationary point a local minimum R* if 2R* > 0. For further information on conditions of optimality, the reader should consult [?].

2.2.1.

2.2.1 Forward Difference

Among the simplest methods of finite difference derivative approximations is the forward step method

          f(R + he )- f(R)
∇ ξif(R ) =-------ii--------+ O(hi)
                 hi
, where ei represents a vector of zeros with the ith index equal to 1, O(hi) represents the truncation error, and hi represents the step size. Herein, hi represents the positive distance from |ξi| to the next larger floating-point number of the same precision as ξi. It should be noted that f may represent either the problem’s objective function m(R) or constraint function c(R).

2.2.2.

2.2.2 Central Difference

Similarly to the forward difference method, the approximate derivative of either the objective or constraint functions may be approximated with a perturbation to the design vector R. Under the central difference paradigm, the finite difference derivative approximation becomes

          f(R-+-hiei)--f(R---hiei)      2
∇ ξif(R ) =          2hi          +O (hi)
It should be noticed that the central difference method is second-order accurate since the dominate term in its truncation error is O(hi2). Therefor the central difference method is more accurate than the forward difference method due to its smaller truncation error.

2.2.3.

2.2.3 Complex Step

Among the best of the finite difference methods is complex-step. It has shown to be very accurate, robust, and easy to implement, while maintaining a reasonable computational cost. The relationship between the real and imaginary parts of the function at hand is exploited to yield a derivative approximation of the following form

                     √---
          ℑ[f(R-+-hiei---1)]     2
∇ ξif(R ) =       hi        + O(hi)

The complex step method converges quadratically with decreasing step size. This method is particularly advantageous because it is insensitive to small step sizes and eventually achieves the accuracy of its function evaluations. The reader can learn more about its derivation in [?]. Herein, focus will be directed towards the use of the complex step method to approximate the gradients of both the objective function m(R) and constraint function C(R). The complex step method can be formulated to approximate the functions’ Jacobian matrices as shown in Listing 2. As an example, to approximate the gradient of the objective function, one should execute obj.Complex_Jacobian(@obj.Stress_Constraints, R).


Listing 2: Complex Step Method
function jac = Complex_Jacobian(obj, funct, R) 
  fval = funct(R); 
  n    = numel(R); 
  m    = numel(fval); 
  jac  = zeros(m,n); 
  h    = eps(R)n; 
  for I=1:n 
    r        = R; 
    r(I,1)   = r(I,1) + h(I,1)i; 
    jac(:,I) = imag(funct(r))/h(I,1); 
  end 
  jac = jac.'; 
end

3.

3 Results

Having instantiated an aircraft with the specifications enumerated in Table 1, one can supply an initial guess to the optimizer. For the sake of testing robustness, the initial guess of the 2n × 1 design vector R0 will characterize the boundary constraints of the spar’s radii, such that {ri,Ri}←{rmin,Rmax}∀i I, yielding an initial total spar mass of m0 = 90.4779 kg, far beyond the desired optimal value m* < 5 kg.

One can easily instantiate an initial guess R0 of the design vector, with n = 20 nodes for instance, as shown in Listing 3.

Listing 3: 20 Node Design Vector
% Take an initial guess of the spar's radii. 
R = zeros(40,1); 
for I=1:Raptor.N_Nodes(R) 
    R(2⋆I-1,1) = Raptor.Rimin; 
    R(2⋆I, 1)  = Raptor.Romax; 
end

Once the design vector has been characterized, the user can easily begin the optimization process by executing [Ropt,fval,exitflag,output,lambda,grad,hessian] = Raptor.Optimize(R, step_type);, where step_type can be 'forward', 'central', or 'complex'. With n = 20 nodes, the forward, central, and complex step optimization processes yield the outputs in Listings 4, 5, and 6, respectively. It can quite easily be seen that all three of the finite difference methods yield approximately equal values of the optimal spar mass m* = 4.9069, thereby verifying beyond a doubt that m* is certainly a local minimum of the optimization problem.

Visually, the spar geometry, vertical displacement, and stress can be see for all the finite difference methods in Figures 2, 4, and 6. Additionally, the iterative convergence of the objective function can be see for each method in Figure 8. It can be seen that each finite difference method yields virtually identical results. Each method yielded a final spar mass that is 63% better than the nominal mass of 13.26 kg. However, it was quite apparent that the optimization process using the complex step method was far more expeditious than the other methods.

4.

4 Conclusions

A virtually identical value of the spar’s optimal mass m* = 4.9069, over 60% better than the nominal mass, has been achieved consistently across three separate finite difference methods. Validity can thereby be concluded for the optimality of the resultant masses and design vectors, as well as the method by which the complex Jacobian matrices were computed in the complex step implementation. Through implementation, as expected, it was seen that the complex step method yielded solution convergence in a fraction of the time it took the forward and central step methods to do so. Lastly, from the geometrical representations of the resulting design vectors, an intuition can be developed, from which a wing’s spar will tend to have greater radii towards its root. It can be seen that the optimization process has indeed solidified this intuition, by yielding geometries which meet the upper constraints towards the root and lower constraints towards the tip.

5.

5 Appendix

5.1.

5.1 Optimization Outputs


Listing 4: Forward Step Output
fval = 
 
    4.9069 
 
 
output = 
 
         iterations: 19 
          funcCount: 848 
          algorithm: 'sqp' 
            message: 'Localminimumfoundthatsatisfiestheconstraints.' 
    constrviolation: 4.7705e-18 
           stepsize: 1.2194e-14 
       lssteplength: 1 
      firstorderopt: 9.1052e-14


Listing 5: Central Step Output
fval = 
 
    4.9069 
 
 
output = 
 
         iterations: 19 
          funcCount: 848 
          algorithm: 'sqp' 
            message: 'Localminimumfoundthatsatisfiestheconstraints.' 
    constrviolation: 4.7705e-18 
           stepsize: 1.2194e-14 
       lssteplength: 1 
      firstorderopt: 9.1052e-14


Listing 6: Complex Step Output
fval = 
 
    4.9069 
 
 
output = 
 
         iterations: 17 
          funcCount: 43 
          algorithm: 'sqp' 
            message: 'Localminimumfoundthatsatisfiestheconstraints.' 
    constrviolation: 4.7705e-18 
           stepsize: 1.8812e-14 
       lssteplength: 0.7000 
      firstorderopt: 8.7522e-14

5.2.

5.2 Optimization Figures



Fig. 2: Forward Difference with 20 Nodes

Geometry

PIC   Vertical Displacement

PIC   Stress

PIC




Fig. 4: Central Difference with 20 Nodes

Geometry

PIC   Vertical Displacement

PIC   Stress

PIC




Fig. 6: Complex Difference with 20 Nodes

Geometry

PIC   Vertical Displacement

PIC   Stress

PIC




Fig. 8: Objective Convergence with 20 Nodes

Forward Difference

PIC   Central Difference

PIC   Complex Difference

PIC


5.3.

5.3 Source Code


Listing 7: UAV Optimization Example
 
% Instantiate the wing 
Raptor = UAV(... % With properties: 
  7.5,       ... % Wing semi-span [m]. 
  1600,      ... % Spar density [kg/m^3]. 
  7e10,      ... % Modulus of elasticity [Pa]. 
  600e6,     ... % Maximum spar strength [Pa]. 
  500,       ... % Mass of aircraft [Kg]. 
  9.807,     ... % Earth's gravity [m/s^2]. 
  2.5e-3,    ... % Minimum spar thickness [m]. 
  1e-2,      ... % Minimum inner spar radius [m]. 
  5e-2,      ... % Maximum outer spar radius [m]. 
  2.5        ... % Maximum operational G-force. 
  ); 
 
% Take an initial guess of the spar's radii. 
R = zeros(80,1); 
for I=1:Raptor.N_Nodes(R) 
    R(2⋆I-1,1) = Raptor.Rimin; 
    R(2⋆I, 1)  = Raptor.Romax; 
end 
 
[Ropt,fval,exitflag,output,lambda,grad,hessian] = Raptor.Optimize(R, 'complex'); 
Raptor.Plot_Spar_Shape(Ropt); 
Raptor.Plot_Spar_Displacement(Ropt); 
Raptor.Plot_Spar_Stress(Ropt);

Listing 8: UAV Class
 
classdef UAV 
  properties 
    Length;   % Wing semi-span [metres]. 
    Density;  % Density of spar [kg/m^3]. 
    E;        % Longitudinal elastic modulus [Pascals]. 
    Strength; % Ultimate tensile/compresive strength [Pa]. 
    Mass;     % Aircraft operational weight [Kg]. 
    Gravity;  % Gravitational acceleration [m/s^2]. 
    Tmin;     % Minimum thickenss of spar [m] 
    Rimin;    % Minimum inner spar radius [m] 
    Romax;    % Maximum outer spar radius [m] 
    Gmax;     % Maximum operational G-force. 
  end 
 
  methods 
    function obj = UAV(L, dens, E, stren, m, g, tmin, rimin, romax, Gmax) 
      % Constructs an instance of the UAV class. 
      obj.Length   = L;     obj.Density = dens;  obj.E       = E; 
      obj.Strength = stren; obj.Mass    = m;     obj.Gravity = g; 
      obj.Tmin     = tmin;  obj.Rimin   = rimin; obj.Romax   = romax; 
      obj.Gmax     = Gmax; 
    end 
 
    function NN = N_Nodes(obj, R) 
      % Returns the integer number of nodes according 
      % to the number of radii given. 
      NN = length(R)/2; 
    end 
 
    function NE = N_Elements(obj, R) 
      % Returns the integer number of finite elements. 
      NE = obj.N_Nodes(R) - 1; 
    end 
 
    function X = Span_Mesh(obj, R) 
      % Returns the spanwise coordinates [m] 
      X = linspace(0, obj.Length, obj.N_Nodes(R)).'; 
    end 
 
    function Iyy = Second_Moment(obj, R) 
      % Returns the second moment of area for a circular 
      % annulus at each spanwise location [m^4]. 
      for I = 1:obj.N_Nodes(R) 
        Iyy(I,1) = (pi/4)⋆(R(2⋆I)^4 - R(2⋆I-1)^4); 
      end 
    end 
 
    function F = Sectional_Force(obj, R) 
      % Returns the wing's force distribution at a G-force [N]. 
      % Gravitational acceleration [m/s^2] 
      g = obj.Gravityobj.Gmax; 
      % Approximately linear force distribution 
      F = g⋆(obj.Mass/obj.Length)⋆(1-(obj.Span_Mesh(R)./obj.Length)); 
    end 
    function Plot_Sectional_Force(obj, R) 
      % Plots the spanwise load distribution. 
      figure; 
      plot(obj.Span_Mesh(R),obj.Sectional_Force(R),'ks-') 
      xlabel('SpanwiseDistance[m]'); 
      ylabel('SectionalForce[N]'); 
    end 
 
    function [SD, SDV] = Spar_Displacement(obj, R) 
      % Returns 2 DoF spar displacement at each node. [m, rad]. 
      SD = CalcBeamDisplacement(obj.Length, obj.E, obj.Second_Moment(R), ... 
                                obj.Sectional_Force(R), obj.N_Elements(R)); 
      SDV = SD(1:2:2⋆(obj.N_Nodes(R))); 
    end 
 
    function Plot_Spar_Displacement(obj, R) 
      % Plots the spar's spanwise vertical displacement. 
      [SD, SDV] = obj.Spar_Displacement(R); 
      figure; 
      plot(obj.Span_Mesh(R), SDV, 'ks-'); 
      axis equal; 
      xlabel('SpanwiseDistance[m]'); 
      ylabel('VerticalSparDisplacement[m]'); 
    end 
 
    function zmax = Max_Height(obj, R) 
      % Returns the maximum height at each node [m]. 
      for I=1:obj.N_Nodes(R) 
          zmax(I,1) = R(2⋆I); 
      end 
    end 
 
    function SS = Spar_Stress(obj, R) 
      % Returns the spar's stress at each node [Pa]. 
      SS = CalcBeamStress(obj.Length, obj.E, obj.Max_Height(R), ... 
                          obj.Spar_Displacement(R), obj.N_Elements(R)); 
    end 
 
    function Plot_Spar_Stress(obj, R) 
      % Plots the spar's spanwise stress. 
      figure; 
      plot(obj.Span_Mesh(R), obj.Spar_Stress(R), 'ks-'); 
      xlabel('SpanwiseDistance[m]'); 
      ylabel('MagnitudeofNormalStress[Pa]') 
    end 
 
    function [V, VS] = Spar_Volume(obj, R) 
      % Returns volume of spar [m^3]. 
      % V = Total volume [m^3], VS = Spanwise volume [m^3]. 
      x = obj.Span_Mesh(R); 
      % Loop over all the elements. 
      for I=1:obj.N_Elements(R) 
        % Index the radii nodes. 
        r1    = R(2⋆I-1,1);   % First inner radius [m]. 
        r2    = R(2⋆I+1,1); % Second inner radius [m]. 
        R1    = R(2⋆I,1);   % First outer radius [m]. 
        R2    = R(2⋆I+2,1); % Second outer radius [m]. 
        x1    = x(I);     % First node location [m]. 
        x2    = x(I+1);   % Second node location [m]. 
        % Volume for this element [m^3]. 
        VS(I,1) = (pi./3)⋆(r1.^2-R1.^2+r1r2+r2.^2-R1R2-R2.^2)⋆(x1-x2); 
      end 
      V = sum(VS); % Summation of volumes. 
    end 
 
    function [m, ms] = Spar_Mass(obj, R) 
      % Returns the mass of the spar [Kg]. 
      % m = Total mass [Kg], ms = Spanwise mass [Kg]. 
      [V, VS] = obj.Spar_Volume(R); 
      m       = V.⋆obj.Density; 
      ms      = VS.⋆obj.Density; 
    end 
 
    function [lb, ub] = Bound_Constraints(obj, R) 
      % Returns the lower and upper bounds for 
      % the design vector. 
      for I=1:obj.N_Nodes(R) 
        lb(2⋆I-1,1) = obj.Rimin; 
        lb(2⋆I,1)   = obj.Rimin + obj.Tmin; 
        ub(2⋆I-1,1) = obj.Romax - obj.Tmin; 
        ub(2⋆I,1)   = obj.Romax; 
      end 
    end 
 
    function c = Stress_Constraints(obj, R) 
      % Returns the nonlinear inequality constraint. 
      c = obj.Spar_Stress(R) - obj.Strength; 
    end 
 
    function [A, b] = Linear_Constraints(obj, R) 
      % Returns the linear constraints, dictating 
      % that the minimum thickness is abided to. 
      for I=1:obj.N_Nodes(R) 
        A(I,2⋆I-1) = 1; 
        A(I,2⋆I)   = -1; 
        b(I,1)     = -obj.Tmin; 
      end 
    end 
 
    function jac = Complex_Jacobian(obj, funct, R) 
      % Returns the Jacobian matrix of a function 
      % using the complex step method. 
      fval = funct(R);    % Function value. 
      n    = numel(R);    % Number of variables. 
      m    = numel(fval); % Number of dependents. 
      jac  = zeros(m,n);  % Memory allocation. 
      h    = eps(R)n;    % Small step size. 
      for I=1:n 
        r        = R;                     % Copy R 
        r(I,1)   = r(I,1) + h(I,1)i;     % Perturb R 
        jac(:,I) = imag(funct(r))/h(I,1); % Differentiate 
      end 
      % Transpose to fit dimensions of problem. 
      jac = jac.'; 
    end 
 
    function [f, fgrad] = Objective_Function(obj, R) 
      f     = obj.Spar_Mass(R);                        % Value 
      fgrad = obj.Complex_Jacobian(@obj.Spar_Mass, R); % Gradient 
    end 
 
    function [c, ceq, gradc, gradceq] = Ineq_Constraints(obj, R) 
      c       = obj.Stress_Constraints(R); % Nonlinear constraint 
      ceq     = [];                        % No ineq constraint 
      % Gradient of nonlinear constraint 
      gradc   = obj.Complex_Jacobian(@obj.Stress_Constraints, R); 
      gradceq = []; % No ineq constraint, no gradient. 
    end 
 
    function [x,fval,exitflag, ... 
              output,lambda,grad,hessian] = Optimize(obj, R, step_type) 
      fun      = @obj.Objective_Function; % Objective function. 
      x0       = R; % Initial guess of spar radii [m]. 
      [A, b]   = obj.Linear_Constraints(R); 
      Aeq      = []; 
      beq      = []; 
      [lb, ub] = obj.Bound_Constraints(R); 
      nonlcon  = @obj.Ineq_Constraints; 
      % Step type specified? 
      if ~exist('step_type', 'var') 
          step_type = 'central'; 
      end 
      if (step_type == 'central') | (step_type == 'forward') 
        % Don't need to specify gradients. 
        objgrad = false; constrgrad = false; 
      end 
      if step_type == 'complex' 
        % Must specify gradients. 
        objgrad = true; constrgrad = true; 
      end 
      options  = optimoptions('fmincon', ... 
                'Display', 'iter-detailed', ... 
                'Algorithm', 'sqp',  ... 
                'UseParallel', true, ... 
                'PlotFcn', @optimplotfval, ... 
                'ScaleProblem', true, ... 
                'SpecifyObjectiveGradient', objgrad, ... 
                'SpecifyConstraintGradient', constrgrad, ... 
                'OptimalityTolerance', 1e-12, ... 
                'StepTolerance', 1e-12, ... 
                'ConstraintTolerance', 1e-12); 
      % Fmincon implementation. 
      [x,fval,exitflag, ... 
       output,lambda,grad,hessian] = fmincon(fun,x0,A,b,Aeq,beq,... 
                                             lb,ub,nonlcon,options); 
    end 
 
    function Plot_Spar_Shape(obj, R) 
      % Returns a plot of the spar's geometry. 
      for I=1:obj.N_Nodes(R) 
        Ri(I) = R(2⋆I-1,1); 
        Ro(I) = R(2⋆I,1); 
      end 
      figure; 
      x = obj.Span_Mesh(R); 
      plot(x,Ri,'ks-',x,Ro,'ks-',x,-Ri,'ks-',x,-Ro,'ks-'); 
      xlabel('SpanwiseDistance[m]'); 
      ylabel('TransverseDistance[m]'); 
    end 
 
  end 
end