Search code examples
pythonnumpyscipymathematical-optimization

Optimizing multiple output variables of a function in Python


I'm currently working on an algorithm that determines the cost of a wind turbine support structure. The algorithm I'm writing needs to optimize the weight of an initial input support structure so that the stress levels will not exceed but be near to the failure criteria of the properties of the materials used. Another requirement is that the natural frequency of the structure needs to be bounded between 2 values. To optimize the structure 4 variables can be altered.

Can I use a function from the Scipy.Optimize library that optimizes the weight of this structure using several design parameters, but keeps into account the natural frequency and the maximum stress value in the support structure?

The function I'm optimizing looks like this:

def func(self, x):
    self.properties.D_mp = x[0]                    # Set a new diameter for the monopile
    self.properties.Dtrat_tower = x[1]             # Set a new thickness ratio for the tower
    self.properties.Dtrat_tp = x[2]                # Set a new thickness ratio for the transition piece  
    self.properties.Dtrat_mud = x[3]               # Set a new thickness ratio for the mudline region of the monopile

    self.UpdateAll()                               # Update the support structure based on the changes in variables above

    eig = self.GetEigenFrequency()                 # Get the natural frequency
    maxUtil = self.GetMaximumUtilisationFactor()   # Get the maximum utilisation ratio on the structure (more than 1 means stress is higher than maximum allowed)

    # Natural frequency of 0.25 and utilisation ratio of 1 are ideal
    # Create some penalty...
    penalty = (100000 * abs((eig - 0.25)))
    penalty +=  (100000 * abs(maxUtil - 1)) 

    return self.GetTotalMass() + penalty

Thanks in advance!


Solution

  • It is probably easiest to make this a single-value optimization problem by folding in the frequency and stress constraints as penalties to the overall fitness, something like

    LOW_COST = 10.
    MID_COST = 150.
    HIGH_COST = 400.
    
    def weight(a, b, c, d):
        return "calculated weight of structure"
    
    def frequency(a, b, c, d):
        return "calculated resonant frequency"
    
    def freq_penalty(freq):
        # Example linear piecewise penalty function -
        #   increasing cost for frequencies below 205 or above 395
        if freq < 205:
            return MID_COST * (205 - freq)
        elif freq < 395:
            return 0.
        else:
            return MID_COST * (freq - 395)
    
    def stress_fraction(a, b, c, d):
        return "calculated stress / failure criteria"
    
    def stress_penalty(stress_frac):
        # Example linear piecewise penalty function -
        #   low extra cost for stress fraction below 0.85,
        #   high extra cost for stress fraction over 0.98
        if stress_frac < 0.85:
            return LOW_COST * (0.85 - stress_frac)
        elif stress_frac < 0.98:
            return 0.
        else:
            return HIGH_COST * (stress_frac - 0.98)
    
    def overall_fitness(parameter_vector):
        a, b, c, d = parameter_vector
        return (
            # D'oh! it took me a while to get this right -
            # we want _minimum_ weight and _minimum_ penalty
            # to get _maximum_ fitness.
           -weight(a, b, c, d)
          - freq_penalty(frequency(a, b, c, d))
          - stress_penalty(stress_fraction(a, b, c, d)
        )
    

    ... you will of course want to find more suitable penalty functions and play with the relative weighting, but this should give you the general idea. Then you can maximize it like

    from scipy.optimize import fmin
    
    initial_guess = [29., 45., 8., 0.06]
    result = fmin(lambda x: -overall_fitness(x), initial_guess, maxfun=100000, full_output=True)
    

    (using the lambda lets fmin (a minimizer) find a maximum value for overall_fitness).

    Alternatively, fmin allows a callback function which is applied after each iteration; you could use this to put hard limits on frequency IF you know how to tweak a,b,c,d appropriately - something like

    def callback(x):
        a, b, c, d = x     # unpack parameter vector
        freq = frequency(a, b, c, d)
        if freq < 205:
            # apply appropriate correction to put frequency back in bounds
            return [a, b, c + 0.2, d]
        elif freq < 395:
            return x
        else:
            return [a, b, c - 0.2, d]