引言

在前面的一篇文章PID控制算法简单说明了模糊控制算法的原理,本文用C++实现了一个通用性的模糊控制器。

fuzzy_controller.hpp文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#pragma once

#include <functional>
#include <map>
#include <string>
#include <vector>

namespace control_algorithm {

/**
* @brief
* 表示模糊集合的类,代表一个模糊集合,包含集合的名称和一个隶属度函数(该函数返回给定值的隶属度)。
*/
class FuzzySet {
public:
std::string name; // 模糊集合的名称
std::function<double(double)> membershipFunction; // 隶属函数

FuzzySet(const std::string& setName, std::function<double(double)> func)
: name(setName), membershipFunction(func) {}

double calculateMembership(double x) const { return membershipFunction(x); }
};

/**
* @brief 模糊控制器类
*/
class Fuzzy {
private:
std::map<std::string, std::vector<FuzzySet>>
inputVariables; // 输入变量及其模糊集合
std::vector<FuzzySet> outputVariable; // 输出变量的模糊集合
std::map<std::string, std::string> ruleBase; // 规则库

public:
Fuzzy(); // 构造函数

void addInputVariable(const std::string& name,
const std::vector<FuzzySet>& sets);

void setOutputVariable(const std::vector<FuzzySet>& sets);

void addRule(const std::string& inputCondition,
const std::string& outputAction);

double evaluate(const std::map<std::string, double>& inputs);

private:
std::map<std::string, double> fuzzify(const std::string& variableName,
double value);
std::map<std::string, double> applyRules(
const std::map<std::string, std::map<std::string, double>>&
fuzzifiedInputs);

double defuzzify(const std::map<std::string, double>& fuzzyOutput);
};

} // namespace control_algorithm

fuzzy_controller.cpp文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121

#include <cmath>
#include <iostream>
#include <numeric>
#include <stdexcept>

#include "fuzzy_controller.hpp"

using namespace control_algorithm;

Fuzzy::Fuzzy() {}

void Fuzzy::addInputVariable(const std::string& name,
const std::vector<FuzzySet>& sets) {
inputVariables[name] = sets;
}

void Fuzzy::setOutputVariable(const std::vector<FuzzySet>& sets) {
outputVariable = sets;
}

void Fuzzy::addRule(const std::string& inputCondition,
const std::string& outputAction) {
ruleBase[inputCondition] = outputAction;
}

std::map<std::string, double> Fuzzy::fuzzify(const std::string& variableName,
double value) {
std::map<std::string, double> memberships;
if (inputVariables.find(variableName) == inputVariables.end()) {
throw std::invalid_argument("Unknown input variable: " + variableName);
}

for (const auto& set : inputVariables[variableName]) {
memberships[set.name] = set.calculateMembership(value);
}
return memberships;
}

std::map<std::string, double> Fuzzy::applyRules(
const std::map<std::string, std::map<std::string, double>>&
fuzzifiedInputs) {
std::map<std::string, double> aggregatedOutput;

for (const auto& rule : ruleBase) {
const std::string& inputCondition = rule.first;
const std::string& outputAction = rule.second;

// Split input conditions by " AND "
double ruleActivation = 1.0;
size_t start = 0;
size_t end = inputCondition.find(" AND ");
while (end != std::string::npos) {
std::string variableAndSet = inputCondition.substr(start, end - start);
size_t splitPos = variableAndSet.find(" ");
std::string variable = variableAndSet.substr(0, splitPos);
std::string set = variableAndSet.substr(splitPos + 1);

// Check if input variable and set exist
if (fuzzifiedInputs.count(variable) == 0 ||
fuzzifiedInputs.at(variable).count(set) == 0) {
ruleActivation = 0.0;
break;
}
ruleActivation =
std::min(ruleActivation, fuzzifiedInputs.at(variable).at(set));
start = end + 5; // Skip " AND "
end = inputCondition.find(" AND ", start);
}

// Handle the last condition
std::string variableAndSet = inputCondition.substr(start);
size_t splitPos = variableAndSet.find(" ");
std::string variable = variableAndSet.substr(0, splitPos);
std::string set = variableAndSet.substr(splitPos + 1);

if (fuzzifiedInputs.count(variable) == 0 ||
fuzzifiedInputs.at(variable).count(set) == 0) {
ruleActivation = 0.0;
} else {
ruleActivation =
std::min(ruleActivation, fuzzifiedInputs.at(variable).at(set));
}

aggregatedOutput[outputAction] =
std::max(aggregatedOutput[outputAction], ruleActivation);
}

return aggregatedOutput;
}

double Fuzzy::defuzzify(const std::map<std::string, double>& fuzzyOutput) {
double numerator = 0.0;
double denominator = 0.0;

for (const auto& set : outputVariable) {
double peak = 0.0; // Replace with actual center of the fuzzy set
// Approximation: assuming peak to be the mean of the set's universe
double lowerBound = -200; // Replace with the actual range
double upperBound = 200; // Replace with the actual range
peak = (lowerBound + upperBound) / 2.0;

if (fuzzyOutput.find(set.name) != fuzzyOutput.end()) {
double membership = fuzzyOutput.at(set.name);
numerator += peak * membership;
denominator += membership;
}
}
return denominator == 0 ? 0 : numerator / denominator;
}

double Fuzzy::evaluate(const std::map<std::string, double>& inputs) {
std::map<std::string, std::map<std::string, double>> fuzzifiedInputs;

for (const auto& input : inputs) {
fuzzifiedInputs[input.first] = fuzzify(input.first, input.second);
}

auto fuzzyOutput = applyRules(fuzzifiedInputs);
return defuzzify(fuzzyOutput);
}