@[toc]
前言
本文以求解二元函数最小值为例,如果需要求解多元函数,只需要修改以下变量即可:
- varNum:变量维度数
- ub和lb:变量的上下界
- vMaxArr:每个维度的搜索速度限制
优化目标
目标:在变量区间范围最小化 Z = x^2 + y^2 - xy - 10x - 4y +60
求解结果
变量取值为:[7.996330803705318, 6.022624753356061]
最优解为:8.000608357127064
迭代结果可视化
算法流程
Java代码
import java.util.Arrays;
import java.util.Random;
/**
* @Author:WSKH
* @ClassName:AFSA_Solve
* @ClassType:
* @Description:
* @Date:2022/6/8/18:26
* @Email:1187560563@qq.com
* @Blog:https://blog.csdn.net/weixin_51545953?type=blog
*/
public class AFSA_Solve {
// 鱼对象
class Fish {
// 当前鱼的坐标(自变量数组)
double[] curVars;
// 当前自变量对应的目标函数值
double curObjValue;
// 适应度(解决最小化问题,所以适应度为目标函数值的倒数)
double fit;
// 全参构造
public Fish(double[] curVars, double curObjValue, double fit) {
this.curVars = curVars;
this.curObjValue = curObjValue;
this.fit = fit;
}
}
// 算法参数
// 变量个数
int varNum = 2;
// 最大迭代次数
int maxGen = 500;
// 鱼群中鱼的个数
int fishNum = 300;
// 每次的最大觅食次数
int preyCnt = 20;
// 鱼的最大感知距离
double visual = 50;
// 可接受的拥挤程度
double crowdedRate = 0.6;
// 步长数组(各个维度的步长)
double[] stepArr = new double[]{6, 6};
// 变量的上下界
double[] ub = new double[]{1000, 1000};
double[] lb = new double[]{-1000, -1000};
// 随机数对象
Random random = new Random();
// 鱼群
Fish[] fishes;
// 最佳的鱼
Fish bestFish;
// 记录迭代过程
public double[][][] positionArr;
// 当前记录的行数
int r;
// 求解主函数
public void solve() {
// 初始化鱼群
initFishes();
// 开始迭代
for (int t = 0; t < maxGen; t++) {
// 聚群行为
AFSwarm();
report();
// 追尾
AFFollow();
report();
// 觅食
AFPrey();
report();
}
for (Fish fish : fishes) {
if (fish.fit > bestFish.fit) {
bestFish = fish;
}
}
// 输出最好的结果
System.out.println("变量取值为:" + Arrays.toString(bestFish.curVars));
System.out.println("最优解为:" + bestFish.curObjValue);
}
// 移动行为(在鱼群的移动过程中,当一条鱼或几条鱼找到食物时,附近的伙伴会迅速移动并到达食物)
void AFFollow() {
for (int i = 0; i < fishes.length; i++) {
// 探索视野内的鱼有多少条,并找到最大适应值的鱼
int friendCount = 0;
Fish maxFish = copyFish(fishes[i]);
for (int j = 0; j < fishes.length; j++) {
if (i != j && getDistance(fishes[i], fishes[j]) <= visual) {
friendCount++;
if (fishes[j].fit > maxFish.fit) {
maxFish = copyFish(fishes[j]);
}
}
}
// 如果周围不拥挤,且两个鱼的目标函数不相等
if ((double) friendCount / fishNum <= crowdedRate && Math.abs(maxFish.curObjValue - fishes[i].curObjValue) > 0.0000001) {
// 尝试往最大适应值位置游
Fish tempFish = copyFish(fishes[i]);
double distance = getDistance(maxFish, tempFish);
for (int m = 0; m < varNum; m++) {
double move = (maxFish.curVars[m] - tempFish.curVars[m]) / (distance) * stepArr[m] * random.nextDouble();
moveFish(tempFish, m, move);
}
tempFish.curObjValue = getObjValue(tempFish.curVars);
tempFish.fit = 1 / tempFish.curObjValue;
if (tempFish.fit > fishes[i].fit) {
fishes[i] = tempFish;
} else {
// 否则,进行觅食
AFPrey(i);
}
} else {
// 否则,进行觅食
AFPrey(i);
}
}
}
// 聚群行为(鱼群在移动的过程中会自然地成群聚集,这是一种生活习惯,可以保证群体的存在,避免危险)
void AFSwarm() {
for (int i = 0; i < fishes.length; i++) {
// 探索视野内的鱼有多少条,并计算周围鱼群的中心
double[] midPoint = new double[varNum];
int friendCount = 0;
for (int j = 0; j < fishes.length; j++) {
if (i != j && getDistance(fishes[i], fishes[j]) <= visual) {
friendCount++;
for (int m = 0; m < varNum; m++) {
midPoint[m] += fishes[j].curVars[m];
}
}
}
// 计算中心
for (int m = 0; m < midPoint.length; m++) {
midPoint[m] /= friendCount;
}
double objValue = getObjValue(midPoint);
// 获取中心的鱼
Fish midFish = new Fish(midPoint, objValue, 1 / objValue);
// 如果中心不拥挤
if ((double) friendCount / fishNum <= crowdedRate) {
// 尝试往中心位置游
Fish tempFish = copyFish(fishes[i]);
double distance = getDistance(midFish, tempFish);
for (int m = 0; m < varNum; m++) {
double move = (midPoint[m] - tempFish.curVars[m]) / (distance) * stepArr[m] * random.nextDouble();
moveFish(tempFish, m, move);
}
tempFish.curObjValue = getObjValue(tempFish.curVars);
tempFish.fit = 1 / tempFish.curObjValue;
if (tempFish.fit > fishes[i].fit) {
fishes[i] = tempFish;
} else {
// 否则,进行觅食
AFPrey(i);
}
} else {
// 否则,进行觅食
AFPrey(i);
}
}
}
// 觅食行为(这是鱼类对食物的基本生物学行为。一般来说,鱼通过视觉感知水中食物的浓度来决定向哪移动,然后选择移动的方向)
void AFPrey(int i) {
// 尝试觅食preyCnt次
for (int j = 0; j < preyCnt; j++) {
Fish tempFish = copyFish(fishes[i]);
for (int m = 0; m < varNum; m++) {
moveFish(tempFish, m, visual * (random.nextDouble() - 0.5) * 2);
}
tempFish.curObjValue = getObjValue(tempFish.curVars);
tempFish.fit = 1 / tempFish.curObjValue;
if (tempFish.fit > fishes[i].fit) {
double distance = getDistance(tempFish, fishes[i]);
// 觅食成功,向成功方向移动
for (int m = 0; m < varNum; m++) {
double move = (tempFish.curVars[m] - fishes[i].curVars[m]) / (distance) * stepArr[m] * random.nextDouble();
moveFish(fishes[i], m, move);
}
fishes[i].curObjValue = getObjValue(fishes[i].curVars);
fishes[i].fit = 1 / fishes[i].curObjValue;
} else {
// 觅食失败,随机移动
for (int m = 0; m < varNum; m++) {
double move = stepArr[m] * (random.nextDouble() - 0.5) * 2;
moveFish(tempFish, m, move);
}
tempFish.curObjValue = getObjValue(tempFish.curVars);
tempFish.fit = 1 / tempFish.curObjValue;
if(tempFish.fit > fishes[i].fit){
fishes[i] = tempFish;
}
}
}
}
void AFPrey(){
for (int i = 0; i < fishes.length; i++) {
AFPrey(i);
}
}
// 记录
void report(){
for (int i = 0; i < fishes.length; i++) {
for (int j = 0; j < varNum; j++) {
positionArr[r][i][j] = fishes[i].curVars[j];
}
}
r++;
}
// 初始化鱼群
private void initFishes() {
positionArr = new double[3*maxGen][fishNum][varNum];
fishes = new Fish[fishNum];
for (int i = 0; i < fishNum; i++) {
fishes[i] = getRandomFish();
if (i == 0 || bestFish.fit < fishes[i].fit) {
bestFish = copyFish(fishes[i]);
}
}
}
// 控制鱼在第m个维度上移动n个距离
public void moveFish(Fish fish, int m, double n) {
// 移动
fish.curVars[m] += n;
// 超出定义域的判断
if (fish.curVars[m] < lb[m]) {
fish.curVars[m] = lb[m];
}
if (fish.curVars[m] > ub[m]) {
fish.curVars[m] = ub[m];
}
}
// 求两条鱼之间的距离
double getDistance(Fish f1, Fish f2) {
double dis = 0d;
for (int i = 0; i < varNum; i++) {
dis += Math.pow(f1.curVars[i] - f2.curVars[i], 2);
}
return Math.sqrt(dis);
}
// 求两点之间的距离
double getDistance(double p1, double p2) {
return Math.sqrt(Math.pow(p1 - p2, 2));
}
// 获取一个随机生成的鱼
Fish getRandomFish() {
double[] vars = new double[varNum];
for (int j = 0; j < vars.length; j++) {
vars[j] = lb[j] + random.nextDouble() * (ub[j] - lb[j]);
}
double objValue = getObjValue(vars);
return new Fish(vars.clone(), objValue, 1 / objValue);
}
/**
* @param vars 自变量数组
* @return 返回目标函数值
*/
public double getObjValue(double[] vars) {
//目标:在变量区间范围最小化 Z = x^2 + y^2 - xy - 10x - 4y +60
return Math.pow(vars[0], 2) + Math.pow(vars[1], 2) - vars[0] * vars[1] - 10 * vars[0] - 4 * vars[1] + 60;
}
// 复制鱼
Fish copyFish(Fish old) {
return new Fish(old.curVars.clone(), old.curObjValue, old.fit);
}
}
可视化代码
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Duration;
/**
* @Author:WSKH
* @ClassName:PlotUtil
* @ClassType:
* @Description:
* @Date:2022/6/6/18:31
* @Email:1187560563@qq.com
* @Blog:https://blog.csdn.net/weixin_51545953?type=blog
*/
public class PlotUtil extends Application {
//当前的时间轴
private Timeline nowTimeline;
//绘图位置坐标
private double[][][] positionArr;
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception {
// 调用算法获取绘图数据
AFSA_Solve solver = new AFSA_Solve();
solver.solve();
positionArr = solver.positionArr;
// 画图
try {
BorderPane root = new BorderPane();
root.setStyle("-fx-padding: 20;");
Scene scene = new Scene(root, 1600, 900);
double canvasWid = 800;
double canvasHei = 800;
//根据画布大小缩放坐标值
this.fixPosition(canvasWid - 100, canvasHei - 100);
//画布和画笔
HBox canvasHbox = new HBox();
Canvas canvas = new Canvas();
canvas.setWidth(canvasWid);
canvas.setHeight(canvasHei);
canvasHbox.setPrefWidth(canvasWid);
canvasHbox.getChildren().add(canvas);
canvasHbox.setAlignment(Pos.CENTER);
canvasHbox.setStyle("-fx-spacing: 20;" +
"-fx-background-color: #87e775;");
root.setTop(canvasHbox);
GraphicsContext paintBrush = canvas.getGraphicsContext2D();
//启动
HBox hBox2 = new HBox();
Button beginButton = new Button("播放迭代过程");
hBox2.getChildren().add(beginButton);
root.setBottom(hBox2);
hBox2.setAlignment(Pos.CENTER);
//启动仿真以及暂停仿真
beginButton.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> {
nowTimeline.play();
});
//创建扫描线连接动画
nowTimeline = new Timeline();
createAnimation(paintBrush);
primaryStage.setScene(scene);
primaryStage.show();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 修正cityPositionArr的坐标,让画出来的点在画布内
*
* @param width
* @param height
*/
private void fixPosition(double width, double height) {
double minX = Double.MAX_VALUE;
double maxX = -Double.MAX_VALUE;
double minY = Double.MAX_VALUE;
double maxY = -Double.MAX_VALUE;
for (int i = 0; i < this.positionArr.length; i++) {
for (int j = 0; j < this.positionArr[0].length; j++) {
minX = Math.min(minX, this.positionArr[i][j][0]);
maxX = Math.max(maxX, this.positionArr[i][j][0]);
minY = Math.min(minY, this.positionArr[i][j][1]);
maxY = Math.max(maxY, this.positionArr[i][j][1]);
}
}
double multiple = Math.max((maxX - minX) / width, (maxY - minY) / height);
//转化为正数数
for (int i = 0; i < this.positionArr.length; i++) {
for (int j = 0; j < this.positionArr[0].length; j++) {
if (minX < 0) {
this.positionArr[i][j][0] = this.positionArr[i][j][0] - minX;
}
if (minY < 0) {
this.positionArr[i][j][1] = this.positionArr[i][j][1] - minY;
}
}
}
for (int i = 0; i < this.positionArr.length; i++) {
for (int j = 0; j < this.positionArr[0].length; j++) {
this.positionArr[i][j][0] = this.positionArr[i][j][0] / multiple;
this.positionArr[i][j][1] = this.positionArr[i][j][1] / multiple;
}
}
}
/**
* 用画笔在画布上画出所有的孔
* 画第i代的所有粒子
*/
private void drawAllCircle(GraphicsContext paintBrush, int i) {
paintBrush.clearRect(0, 0, 2000, 2000);
paintBrush.setFill(Color.RED);
for (int j = 0; j < this.positionArr[i].length; j++) {
drawCircle(paintBrush, i, j);
}
}
/**
* 用画笔在画布上画出一个孔
* 画第i代的第j个粒子
*/
private void drawCircle(GraphicsContext paintBrush, int i, int j) {
double x = this.positionArr[i][j][0];
double y = this.positionArr[i][j][1];
double radius = 2;
// 圆的直径
double diameter = radius * 2;
paintBrush.fillOval(x, y, diameter, diameter);
}
/**
* 创建动画
*/
private void createAnimation(GraphicsContext paintBrush) {
for (int i = 0; i < this.positionArr[0].length; i++) {
int finalI = i;
KeyFrame keyFrame = new KeyFrame(Duration.seconds(i * 0.05), event -> drawAllCircle(paintBrush, finalI));
nowTimeline.getKeyFrames().add(keyFrame);
}
}
}