Implementation of continuous state transition algorithm (STA) (python version)

Posted by Tim L on Thu, 16 Dec 2021 08:52:58 +0100

Background introduction

All stochastic optimization algorithms (GA, ABC, PSO, STA, simulated annealing) are a routine

  1. Generating a pile of new candidate solutions through the current solution or some is called generating candidate solutions
  2. Then update the solution through an evaluation function (evaluation standard), that is, find the good one or some in a pile
  3. Keep cycling until the termination conditions are met

operator

The so-called operator is actually a rule for generating candidate solutions. You can understand that if you give an operator a certain input, he will return one or more candidate solutions to you

Operator description of continuous STA

There are four basic operators, namely, rotation transformation, expansion transformation, axial transformation and translation transformation

Rotation transformation

A new solution is generated by using the rotation transformation formula. Physically, it is a solution to the hypersphere with Best as the center and alpha as the radius

Telescopic transformation

The scaling transformation formula generates a new solution. Physically, it is possible to scale every dimension of Best to (- ∞, + ∞), and then constrain it to the boundary of the definition domain, which belongs to global search

Axial transformation

The new solution is generated by using the axial transformation formula. Physically, it is possible to scale a single dimension of Best to (- ∞, + ∞), which belongs to local search and enhances the single-dimensional search ability

Translation transformation

The new solution is generated by using the translation formula. Physically, the new solution is generated by searching on the line oldBest newBest. We believe that there is a high probability that a good solution will appear on the connection between the old and new optimal solutions. For example, when the two solutions are on both sides of the valley bottom, the above figure is scaled in only one dimension

file structure

         

         

code

benchmark.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from functools import reduce
from operator import mul
import numpy as np
import math


def Sphere(s):
	square = map(lambda y: y * y ,s)
	return sum(square)

def Rosenbrock(s):
	square = map(lambda x, y: 100 * (y - x**2)**2 + (x - 1)**2 , s[:len(s)-1],s[1:len(s)])
	return  sum(square)

def Rastrigin(s):
	square = map(lambda x: x**2 - 10 * math.cos(2*math.pi*x) + 10, s)
	return  float(sum(square))

def Michalewicz(s):
	n = len(s)
	t1 = -sum(map(lambda x, y: math.sin(x) * (math.sin( y*x**2/math.pi ))**20, s, range(1,n+1)))
	return t1

def Griewank(s): 
	t1 = sum(map(lambda x: 1/4000 * x**2, s))
	n = len(s)
	t2 = map(lambda x, y: math.cos(x/np.sqrt(y)), s, range(1,n+1))
	t3 = reduce(mul, t2)
	return t1 - t3 + 1

STA.py

# -*- coding: utf-8 -*-
"""
Created on Sat Nov  6 10:57:13 2021

@author: Intelligent control and optimization decision-making research group, School of automation, Central South University
"""

import numpy as np
import time

class STA():
    # initialization
    def __init__(self,funfcn,Range,SE,Maxiter,dict_opt):
        self.funfcn = funfcn
        self.Range = Range
        self.SE = SE
        self.Maxiter = Maxiter
        self.dict_opt = dict_opt
        self.Dim = self.Range.shape[1]

    def initialization(self):
        self.Best = np.array(self.Range[0,:] + self.Range[1,:]-self.Range[1,:]*np.random.uniform(0,1,(1,self.Range.shape[1])))
        self.fBest = self.funfcn(self.Best[0])
        self.history = []
        self.history.append(self.fBest)
        
    """Using the rotation transformation formula to produce a new solution is physically correct Best Center alpha Sample the hypersphere of radius SE A solution"""
    def op_rotate(self):
        R1 = np.random.uniform(-1,1,(self.SE,self.Dim))
        R2 = np.random.uniform(-1,1,(self.SE,1))
        a = np.tile(self.Best,(self.SE,1))
        b = np.tile(R2,(1,self.Dim))
        c = R1/np.tile(np.linalg.norm(R1,axis=1,keepdims = True),(1,self.Dim))
        State = a + self.dict_opt['alpha']*b*c
        return State

    """Using the expansion transformation formula to produce a new solution is physically correct Best Every dimension of can be scaled to(-∞,+∞),Then it is constrained to the boundary of the definition domain, which belongs to global search"""
    def op_expand(self): 
        a = np.tile(self.Best,(self.SE,1))
        b = np.random.randn(self.SE,self.Dim)
        State = a + self.dict_opt['gamma'] * b * a
        return State

    """Using the axial transformation formula to produce a new solution is physically correct Best A single dimension of can scale to(-∞,+∞),It belongs to local search and enhances the ability of one-dimensional search"""
    def op_axes(self):
        #Realize one
        State = np.tile(self.Best,(self.SE,1))
        for i in range(self.SE):
            index = np.random.randint(0,self.Dim)
            State[i,index] = State[i,index] + self.dict_opt['delta']*np.random.randn()*State[i,index]
        return State
        #Implementation II
#        a = np.tile(self.Best,(self.SE,1))
#        A = np.zeros((self.SE,self.Dim))
#        A[np.arange(self.SE),np.random.randint(0,self.Dim,self.SE)] = np.random.randn(self.SE)
#        State = a + A*a
#        return State
    
    """Using the translation formula to generate a new solution is physically oldBest - newBest Search on this line to generate a new solution. We believe that there is a high probability that a good solution will appear on the connection between the old and new optimal solutions, such as when the two solutions are on both sides of the valley bottom"""
    def op_translate(self,oldBest):
        a = np.tile(self.Best,(self.SE,1)) # SE * n
        b = np.random.uniform(0,1,(self.SE,1))
        c = self.dict_opt['beta']*(self.Best - oldBest)/(np.linalg.norm(self.Best - oldBest)) # 1*n
        State = a + b * c
        return State

    def selection(self,State):
        fState = np.zeros((self.SE,1)) #
        for i in range(self.SE):
            fState[i] = self.funfcn(State[i,:])
        index = np.argmin(fState)
        return State[index,:],fState[index,:]
    
    def bound(self,State):
        Pop_Lb = np.tile(self.Range[0],(State.shape[0],1))
        Pop_Ub = np.tile(self.Range[1],(State.shape[0],1))
        changeRows = State > Pop_Ub
        State[changeRows] = Pop_Ub[changeRows]
        changeRows = State < Pop_Lb
        State[changeRows] = Pop_Lb[changeRows]
        return State       

    def run(self):
        time_start = time.time()
        self.initialization()
        for i in range(self.Maxiter):
            if self.dict_opt['alpha'] < 1e-4:
                self.dict_opt['alpha'] = 1.0
            else:
                self.dict_opt['alpha'] = 0.5*self.dict_opt['alpha']

            """The three operators are used to generate a new solution and update the optimal solution"""
            dict_op = {"rotate": self.op_rotate(), "expand": self.op_expand(),"axes": self.op_axes()}
            for key in dict_op:
                oldBest = self.Best
                State = self.bound(dict_op[key])
                newBest, fnewBest = self.selection(State)
                if fnewBest < self.fBest:      # If the operator (any one of the three) produces a better solution than history, call the translate operator again to see if it can find a better solution
                    self.fBest, self.Best = fnewBest, newBest
                    State = self.bound(self.op_translate(oldBest))
                    newBest, fnewBest = self.selection(State)
                    if fnewBest < self.fBest:
                        self.fBest, self.Best = fnewBest, newBest

            self.history.append(self.fBest[0])
            print("The first{}The optimal value of this iteration is:{:.5e}".format(i,self.fBest[0]))

            # "" "let the algorithm stop in advance if the update amount of the two optimal values is less than 1e-10" ""
            # if (abs(self.history[i+1] - self.history[i]) < 1e-10) & (self.dict_opt['alpha'] < 1e-4):
            #     self.history = np.array(self.history[1:])
            #     break
        time_end = time.time()
        print("The total time is:{:.5f}".format(time_end - time_start))

Test.py

# -*- coding: utf-8 -*-
"""
Created on Sat Nov  6 10:57:13 2021

@author: Intelligent control and optimization decision-making research group, School of automation, Central South University
"""

from STA import STA
import Benchmark
import numpy as np
import matplotlib.pyplot as plt

#Parameter setting
funfcn = Benchmark.Sphere
Dim = 100
Range = np.repeat([-30,30],Dim).reshape(-1,Dim)
SE = 30
Maxiter = 1000
dict_opt = {'alpha':1,'beta':1,'gamma':1,'delta':1}
# Algorithm call
sta = STA(funfcn,Range,SE,Maxiter,dict_opt)
sta.run()
# Drawing
#x = np.arange(1,Maxiter+2).reshape(-1,1)
y = sta.history
x = np.arange(len(y)).reshape(-1,1)
plt.semilogy(x,y,'b-o')
plt.xlabel('Ierations')
plt.ylabel('fitness')
plt.show()

result

This is the of the Sphere function

Postscript

You can change the function test by yourself. You can see more test functions Unconstrained benchmark function , test by writing functions

Topics: Python Algorithm