diff options
author | Johannes Schauer <josch@debian.org> | 2015-07-07 10:12:00 +0100 |
---|---|---|
committer | Johannes Schauer <josch@debian.org> | 2015-07-07 10:12:00 +0100 |
commit | 0e8c1c9d31d15034b1ff1062c9bf0bfcdc849dd9 (patch) | |
tree | 840a335d9a4487d3f1909a31c8526a2e647809e7 /fuzzylite/src/rule |
Import fuzzylite_5.1+dfsg.orig.tar.xz
[dgit import orig fuzzylite_5.1+dfsg.orig.tar.xz]
Diffstat (limited to 'fuzzylite/src/rule')
-rw-r--r-- | fuzzylite/src/rule/Antecedent.cpp | 368 | ||||
-rw-r--r-- | fuzzylite/src/rule/Consequent.cpp | 243 | ||||
-rw-r--r-- | fuzzylite/src/rule/Expression.cpp | 83 | ||||
-rw-r--r-- | fuzzylite/src/rule/Rule.cpp | 262 | ||||
-rw-r--r-- | fuzzylite/src/rule/RuleBlock.cpp | 210 |
5 files changed, 1166 insertions, 0 deletions
diff --git a/fuzzylite/src/rule/Antecedent.cpp b/fuzzylite/src/rule/Antecedent.cpp new file mode 100644 index 0000000..aa18a68 --- /dev/null +++ b/fuzzylite/src/rule/Antecedent.cpp @@ -0,0 +1,368 @@ +/* + Author: Juan Rada-Vilela, Ph.D. + Copyright (C) 2010-2014 FuzzyLite Limited + All rights reserved + + This file is part of fuzzylite. + + fuzzylite is free software: you can redistribute it and/or modify it under + the terms of the GNU Lesser General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + fuzzylite is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + for more details. + + You should have received a copy of the GNU Lesser General Public License + along with fuzzylite. If not, see <http://www.gnu.org/licenses/>. + + fuzzylite™ is a trademark of FuzzyLite Limited. + + */ + +#include "fl/rule/Antecedent.h" + +#include "fl/Engine.h" +#include "fl/factory/HedgeFactory.h" +#include "fl/factory/FactoryManager.h" +#include "fl/hedge/Any.h" +#include "fl/hedge/Hedge.h" +#include "fl/norm/SNorm.h" +#include "fl/norm/TNorm.h" +#include "fl/rule/Expression.h" +#include "fl/rule/Rule.h" +#include "fl/term/Accumulated.h" +#include "fl/term/Function.h" +#include "fl/term/Term.h" +#include "fl/variable/InputVariable.h" +#include "fl/variable/OutputVariable.h" + +#include <algorithm> +#include <stack> + + +namespace fl { + + Antecedent::Antecedent() + : _text(""), _expression(fl::null) { + } + + Antecedent::~Antecedent() { + unload(); + } + + void Antecedent::setText(const std::string& text) { + this->_text = text; + } + + std::string Antecedent::getText() const { + return this->_text; + } + + Expression* Antecedent::getExpression() const { + return this->_expression; + } + + bool Antecedent::isLoaded() const { + return this->_expression != fl::null; + } + + scalar Antecedent::activationDegree(const TNorm* conjunction, const SNorm* disjunction) const { + return this->activationDegree(conjunction, disjunction, this->_expression); + } + + scalar Antecedent::activationDegree(const TNorm* conjunction, const SNorm* disjunction, + const Expression* node) const { + if (not isLoaded()) { + throw fl::Exception("[antecedent error] antecedent <" + _text + "> is not loaded", FL_AT); + } + const Proposition* proposition = dynamic_cast<const Proposition*> (node); + if (proposition) { + if (not proposition->variable->isEnabled()) { + return 0.0; + } + + if (not proposition->hedges.empty()) { + //if last hedge is "Any", apply hedges in reverse order and return degree + std::vector<Hedge*>::const_reverse_iterator rit = proposition->hedges.rbegin(); + if (dynamic_cast<Any*> (*rit)) { + scalar result = (*rit)->hedge(fl::nan); + while (++rit != proposition->hedges.rend()) { + result = (*rit)->hedge(result); + } + return result; + } + } + scalar result = fl::nan; + if (InputVariable * inputVariable = dynamic_cast<InputVariable*> (proposition->variable)) { + result = proposition->term->membership(inputVariable->getInputValue()); + } else if (OutputVariable * outputVariable = dynamic_cast<OutputVariable*> (proposition->variable)) { + result = outputVariable->fuzzyOutput()->activationDegree(proposition->term); + } + for (std::vector<Hedge*>::const_reverse_iterator rit = proposition->hedges.rbegin(); + rit != proposition->hedges.rend(); ++rit) { + result = (*rit)->hedge(result); + } + return result; + } + //if node is an operator + const Operator* fuzzyOperator = dynamic_cast<const Operator*> (node); + if (not (fuzzyOperator->left and fuzzyOperator->right)) { + std::ostringstream ex; + ex << "[syntax error] left and right operands must exist"; + throw fl::Exception(ex.str(), FL_AT); + } + if (fuzzyOperator->name == Rule::andKeyword()) { + if (not conjunction) throw fl::Exception("[conjunction error] " + "the following rule requires a conjunction operator:\n" + _text, FL_AT); + return conjunction->compute( + this->activationDegree(conjunction, disjunction, fuzzyOperator->left), + this->activationDegree(conjunction, disjunction, fuzzyOperator->right)); + } + + if (fuzzyOperator->name == Rule::orKeyword()) { + if (not disjunction) throw fl::Exception("[disjunction error] " + "the following rule requires a disjunction operator:\n" + _text, FL_AT); + return disjunction->compute( + this->activationDegree(conjunction, disjunction, fuzzyOperator->left), + this->activationDegree(conjunction, disjunction, fuzzyOperator->right)); + } + std::ostringstream ex; + ex << "[syntax error] operator <" << fuzzyOperator->name << "> not recognized"; + throw fl::Exception(ex.str(), FL_AT); + + } + + void Antecedent::unload() { + if (_expression) { + delete _expression; + _expression = fl::null; + } + } + + void Antecedent::load(fl::Rule* rule, const Engine* engine) { + load(_text, rule, engine); + } + + void Antecedent::load(const std::string& antecedent, fl::Rule* rule, const Engine* engine) { + FL_DBG("Antecedent: " << antecedent); + unload(); + this->_text = antecedent; + if (fl::Op::trim(antecedent).empty()) { + throw fl::Exception("[syntax error] antecedent is empty", FL_AT); + } + /* + Builds an proposition tree from the antecedent of a fuzzy rule. + The rules are: + 1) After a variable comes 'is', + 2) After 'is' comes a hedge or a term + 3) After a hedge comes a hedge or a term + 4) After a term comes a variable or an operator + */ + + Function function; + + std::string postfix = function.toPostfix(antecedent); + FL_DBG("Postfix: " << postfix); + std::stringstream tokenizer(postfix); + std::string token; + + enum FSM { + S_VARIABLE = 1, S_IS = 2, S_HEDGE = 4, S_TERM = 8, S_AND_OR = 16 + }; + int state = S_VARIABLE; + std::stack<Expression*> expressionStack; + Proposition* proposition = fl::null; + try { + while (tokenizer >> token) { + if (state bitand S_VARIABLE) { + Variable* variable = fl::null; + if (engine->hasInputVariable(token)) variable = engine->getInputVariable(token); + else if (engine->hasOutputVariable(token)) variable = engine->getOutputVariable(token); + if (variable) { + proposition = new Proposition; + proposition->variable = variable; + expressionStack.push(proposition); + + state = S_IS; + FL_DBG("Token <" << token << "> is variable"); + continue; + } + } + + if (state bitand S_IS) { + if (token == Rule::isKeyword()) { + state = S_HEDGE bitor S_TERM; + FL_DBG("Token <" << token << "> is keyword"); + continue; + } + } + + if (state bitand S_HEDGE) { + Hedge* hedge = rule->getHedge(token); + if (not hedge) { + HedgeFactory* factory = FactoryManager::instance()->hedge(); + if (factory->hasConstructor(token)) { + hedge = factory->constructObject(token); + rule->addHedge(hedge); + } + } + if (hedge) { + proposition->hedges.push_back(hedge); + if (dynamic_cast<Any*> (hedge)) { + state = S_VARIABLE bitor S_AND_OR; + } else { + state = S_HEDGE bitor S_TERM; + } + FL_DBG("Token <" << token << "> is hedge"); + continue; + } + } + + if (state bitand S_TERM) { + if (proposition->variable->hasTerm(token)) { + proposition->term = proposition->variable->getTerm(token); + state = S_VARIABLE bitor S_AND_OR; + FL_DBG("Token <" << token << "> is term"); + continue; + } + } + + if (state bitand S_AND_OR) { + if (token == Rule::andKeyword() or token == Rule::orKeyword()) { + if (expressionStack.size() < 2) { + std::ostringstream ex; + ex << "[syntax error] logical operator <" << token << "> expects two operands," + << "but found <" << expressionStack.size() << "> in antecedent"; + throw fl::Exception(ex.str(), FL_AT); + } + Operator* fuzzyOperator = new Operator; + fuzzyOperator->name = token; + fuzzyOperator->right = expressionStack.top(); + expressionStack.pop(); + fuzzyOperator->left = expressionStack.top(); + expressionStack.pop(); + expressionStack.push(fuzzyOperator); + + state = S_VARIABLE bitor S_AND_OR; + FL_DBG("Subtree: " << fuzzyOperator->toString() << + "(" << fuzzyOperator->left->toString() << ") " << + "(" << fuzzyOperator->right->toString() << ")"); + continue; + } + } + + //If reached this point, there was an error + if ((state bitand S_VARIABLE) or (state bitand S_AND_OR)) { + std::ostringstream ex; + ex << "[syntax error] antecedent expected variable or logical operator, but found <" << token << ">"; + throw fl::Exception(ex.str(), FL_AT); + } + if (state bitand S_IS) { + std::ostringstream ex; + ex << "[syntax error] antecedent expected keyword <" << Rule::isKeyword() << ">, but found <" << token << ">"; + throw fl::Exception(ex.str(), FL_AT); + } + if ((state bitand S_HEDGE) or (state bitand S_TERM)) { + std::ostringstream ex; + ex << "[syntax error] antecedent expected hedge or term, but found <" << token << ">"; + throw fl::Exception(ex.str(), FL_AT); + } + std::ostringstream ex; + ex << "[syntax error] unexpected token <" << token << "> in antecedent"; + throw fl::Exception(ex.str(), FL_AT); + } + + if (not ((state bitand S_VARIABLE) or (state bitand S_AND_OR))) { //only acceptable final state + if (state bitand S_IS) { + std::ostringstream ex; + ex << "[syntax error] antecedent expected keyword <" << Rule::isKeyword() << "> after <" << token << ">"; + throw fl::Exception(ex.str(), FL_AT); + } + if ((state bitand S_HEDGE) or (state bitand S_TERM)) { + std::ostringstream ex; + ex << "[syntax error] antecedent expected hedge or term after <" << token << ">"; + throw fl::Exception(ex.str(), FL_AT); + } + } + + if (expressionStack.size() != 1) { + std::vector<std::string> errors; + while (expressionStack.size() > 1) { + Expression* expression = expressionStack.top(); + expressionStack.pop(); + errors.push_back(expression->toString()); + delete expression; + } + std::ostringstream ex; + ex << "[syntax error] unable to parse the following expressions in antecedent <" + << Op::join(errors, " ") << ">"; + throw fl::Exception(ex.str(), FL_AT); + } + } catch (...) { + for (std::size_t i = 0; i < expressionStack.size(); ++i) { + delete expressionStack.top(); + expressionStack.pop(); + } + throw; + } + this->_expression = expressionStack.top(); + } + + std::string Antecedent::toString() const { + return toInfix(this->_expression); + } + + std::string Antecedent::toPrefix(const Expression* node) const { + if (not isLoaded()) { + throw fl::Exception("[antecedent error] antecedent <" + _text + "> is not loaded", FL_AT); + } + if (not node) node = this->_expression; + + if (dynamic_cast<const Proposition*> (node)) { + return node->toString(); + } + const Operator* fuzzyOperator = dynamic_cast<const Operator*> (node); + std::stringstream ss; + ss << fuzzyOperator->toString() << " " + << toPrefix(fuzzyOperator->left) << " " + << toPrefix(fuzzyOperator->right) << " "; + return ss.str(); + } + + std::string Antecedent::toInfix(const Expression* node) const { + if (not isLoaded()) { + throw fl::Exception("[antecedent error] antecedent <" + _text + "> is not loaded", FL_AT); + } + if (not node) node = this->_expression; + if (dynamic_cast<const Proposition*> (node)) { + return node->toString(); + } + const Operator* fuzzyOperator = dynamic_cast<const Operator*> (node); + std::stringstream ss; + ss << toInfix(fuzzyOperator->left) << " " + << fuzzyOperator->toString() << " " + << toInfix(fuzzyOperator->right) << " "; + return ss.str(); + } + + std::string Antecedent::toPostfix(const Expression* node) const { + if (not isLoaded()) { + throw fl::Exception("[antecedent error] antecedent <" + _text + "> is not loaded", FL_AT); + } + if (not node) node = this->_expression; + if (dynamic_cast<const Proposition*> (node)) { + return node->toString(); + } + const Operator* fuzzyOperator = dynamic_cast<const Operator*> (node); + std::stringstream ss; + ss << toPostfix(fuzzyOperator->left) << " " + << toPostfix(fuzzyOperator->right) << " " + << fuzzyOperator->toString() << " "; + return ss.str(); + } + + +} diff --git a/fuzzylite/src/rule/Consequent.cpp b/fuzzylite/src/rule/Consequent.cpp new file mode 100644 index 0000000..28d3390 --- /dev/null +++ b/fuzzylite/src/rule/Consequent.cpp @@ -0,0 +1,243 @@ +/* + Author: Juan Rada-Vilela, Ph.D. + Copyright (C) 2010-2014 FuzzyLite Limited + All rights reserved + + This file is part of fuzzylite. + + fuzzylite is free software: you can redistribute it and/or modify it under + the terms of the GNU Lesser General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + fuzzylite is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + for more details. + + You should have received a copy of the GNU Lesser General Public License + along with fuzzylite. If not, see <http://www.gnu.org/licenses/>. + + fuzzylite™ is a trademark of FuzzyLite Limited. + + */ + +#include "fl/rule/Consequent.h" + +#include "fl/Engine.h" +#include "fl/factory/HedgeFactory.h" +#include "fl/factory/FactoryManager.h" +#include "fl/hedge/Hedge.h" +#include "fl/hedge/Any.h" +#include "fl/norm/TNorm.h" +#include "fl/rule/Expression.h" +#include "fl/rule/Rule.h" +#include "fl/term/Accumulated.h" +#include "fl/term/Activated.h" +#include "fl/variable/OutputVariable.h" + +#include <algorithm> + +namespace fl { + + Consequent::Consequent() { + } + + Consequent::~Consequent() { + unload(); + } + + std::string Consequent::getText() const { + return this->_text; + } + + void Consequent::setText(const std::string& text) { + this->_text = text; + } + + const std::vector<Proposition*>& Consequent::conclusions() const { + return this->_conclusions; + } + + void Consequent::modify(scalar activationDegree, const TNorm* activation) { + if (not isLoaded()) { + throw fl::Exception("[consequent error] consequent <" + _text + "> is not loaded", FL_AT); + } + for (std::size_t i = 0; i < _conclusions.size(); ++i) { + Proposition* proposition = _conclusions.at(i); + if (proposition->variable->isEnabled()) { + if (not proposition->hedges.empty()) { + for (std::vector<Hedge*>::const_reverse_iterator rit = proposition->hedges.rbegin(); + rit != proposition->hedges.rend(); ++rit) { + activationDegree = (*rit)->hedge(activationDegree); + } + } + Activated* term = new Activated(_conclusions.at(i)->term, activationDegree, activation); + OutputVariable* outputVariable = dynamic_cast<OutputVariable*> (proposition->variable); + outputVariable->fuzzyOutput()->addTerm(term); + FL_DBG("Accumulating " << term->toString()); + } + } + } + + bool Consequent::isLoaded() { + return not _conclusions.empty(); + } + + void Consequent::unload() { + for (std::size_t i = 0; i < _conclusions.size(); ++i) { + delete _conclusions.at(i); + } + _conclusions.clear(); + } + + void Consequent::load(Rule* rule, const Engine* engine) { + load(_text, rule, engine); + } + + void Consequent::load(const std::string& consequent, Rule* rule, const Engine* engine) { + unload(); + this->_text = consequent; + + if (fl::Op::trim(consequent).empty()) { + throw fl::Exception("[syntax error] consequent is empty", FL_AT); + } + + /** + Extracts the list of propositions from the consequent + The rules are: + 1) After a variable comes 'is' or '=', + 2) After 'is' comes a hedge or a term + 3) After a hedge comes a hedge or a term + 4) After a term comes operators 'and' or 'with' + 5) After operator 'and' comes a variable + 6) After operator 'with' comes a float + */ + enum FSM { + S_VARIABLE = 1, S_IS = 2, S_HEDGE = 4, S_TERM = 8, + S_AND = 16, S_WITH = 32 + }; + int state = S_VARIABLE; + + Proposition* proposition = fl::null; + + std::stringstream tokenizer(consequent); + std::string token; + try { + while (tokenizer >> token) { + if (state bitand S_VARIABLE) { + if (engine->hasOutputVariable(token)) { + proposition = new Proposition; + proposition->variable = engine->getOutputVariable(token); + _conclusions.push_back(proposition); + + state = S_IS; + continue; + } + } + + if (state bitand S_IS) { + if (token == Rule::isKeyword()) { + state = S_HEDGE bitor S_TERM; + continue; + } + } + + if (state bitand S_HEDGE) { + Hedge* hedge = rule->getHedge(token); + if (not hedge) { + HedgeFactory* factory = FactoryManager::instance()->hedge(); + if (factory->hasConstructor(token)){ + hedge = factory->constructObject(token); + rule->addHedge(hedge); + } + } + if (hedge) { + proposition->hedges.push_back(hedge); + state = S_HEDGE bitor S_TERM; + continue; + } + } + + if (state bitand S_TERM) { + if (proposition->variable->hasTerm(token)) { + proposition->term = proposition->variable->getTerm(token); + state = S_AND bitor S_WITH; + continue; + } + } + + if (state bitand S_AND) { + if (token == Rule::andKeyword()) { + state = S_VARIABLE; + continue; + } + } + + //if reached this point, there was an error: + if (state bitand S_VARIABLE) { + std::ostringstream ex; + ex << "[syntax error] consequent expected output variable, but found <" << token << ">"; + throw fl::Exception(ex.str(), FL_AT); + } + if (state bitand S_IS) { + std::ostringstream ex; + ex << "[syntax error] consequent expected keyword <" << Rule::isKeyword() << ">, " + "but found <" << token << ">"; + throw fl::Exception(ex.str(), FL_AT); + } + + if ((state bitand S_HEDGE) or (state bitand S_TERM)) { + std::ostringstream ex; + ex << "[syntax error] consequent expected hedge or term, but found <" << token << ">"; + throw fl::Exception(ex.str(), FL_AT); + } + + if ((state bitand S_AND) or (state bitand S_WITH)) { + std::ostringstream ex; + ex << "[syntax error] consequent expected operator <" << Rule::andKeyword() << "> " + << "or keyword <" << Rule::withKeyword() << ">, " + << "but found <" << token << ">"; + throw fl::Exception(ex.str(), FL_AT); + } + + std::ostringstream ex; + ex << "[syntax error] unexpected token <" << token << "> in consequent"; + throw fl::Exception(ex.str(), FL_AT); + } + + if (not ((state bitand S_AND) or (state bitand S_WITH))) { //only acceptable final state + if (state bitand S_VARIABLE) { + std::ostringstream ex; + ex << "[syntax error] consequent expected output variable after <" << token << ">"; + throw fl::Exception(ex.str(), FL_AT); + } + if (state bitand S_IS) { + std::ostringstream ex; + ex << "[syntax error] consequent expected keyword <" << Rule::isKeyword() << "> " + "after <" << token << ">"; + throw fl::Exception(ex.str(), FL_AT); + } + if ((state bitand S_HEDGE) or (state bitand S_TERM)) { + std::ostringstream ex; + ex << "[syntax error] consequent expected hedge or term after <" << token << ">"; + throw fl::Exception(ex.str(), FL_AT); + } + } + } catch (...) { + unload(); + throw; + } + } + + std::string Consequent::toString() const { + std::stringstream ss; + for (std::size_t i = 0; i < _conclusions.size(); ++i) { + ss << _conclusions.at(i)->toString(); + if (i + 1 < _conclusions.size()) + ss << " " << Rule::andKeyword() << " "; + } + return ss.str(); + } + +} diff --git a/fuzzylite/src/rule/Expression.cpp b/fuzzylite/src/rule/Expression.cpp new file mode 100644 index 0000000..ea7873e --- /dev/null +++ b/fuzzylite/src/rule/Expression.cpp @@ -0,0 +1,83 @@ +/* + Author: Juan Rada-Vilela, Ph.D. + Copyright (C) 2010-2014 FuzzyLite Limited + All rights reserved + + This file is part of fuzzylite. + + fuzzylite is free software: you can redistribute it and/or modify it under + the terms of the GNU Lesser General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + fuzzylite is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + for more details. + + You should have received a copy of the GNU Lesser General Public License + along with fuzzylite. If not, see <http://www.gnu.org/licenses/>. + + fuzzylite™ is a trademark of FuzzyLite Limited. + + */ + +#include "fl/rule/Expression.h" + +#include "fl/hedge/Hedge.h" +#include "fl/term/Term.h" +#include "fl/rule/Rule.h" +#include "fl/variable/Variable.h" + +namespace fl { + + Expression::Expression() { + } + + Expression::~Expression() { + } + + Proposition::Proposition() + : Expression(), variable(fl::null), term(fl::null) { + } + + Proposition::~Proposition() { + + } + + std::string Proposition::toString() const { + std::ostringstream ss; + if (variable) { + ss << variable->getName(); + } else { + ss << "?"; + } + if (not hedges.empty()) { + ss << " " << Rule::isKeyword() << " "; + for (std::size_t i = 0; i < hedges.size(); ++i) { + ss << hedges.at(i)->name() << " "; + } + } + + if (term) { //term is fl::null if hedge is any + if (hedges.empty()) { + ss << " " << Rule::isKeyword() << " "; + } + ss << term->getName(); + } + return ss.str(); + } + + Operator::Operator() : Expression(), name(""), left(fl::null), right(fl::null) { + } + + Operator::~Operator() { + if (left) delete left; + if (right) delete right; + } + + std::string Operator::toString() const { + return name; + } + +} diff --git a/fuzzylite/src/rule/Rule.cpp b/fuzzylite/src/rule/Rule.cpp new file mode 100644 index 0000000..446290b --- /dev/null +++ b/fuzzylite/src/rule/Rule.cpp @@ -0,0 +1,262 @@ +/* + Author: Juan Rada-Vilela, Ph.D. + Copyright (C) 2010-2014 FuzzyLite Limited + All rights reserved + + This file is part of fuzzylite. + + fuzzylite is free software: you can redistribute it and/or modify it under + the terms of the GNU Lesser General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + fuzzylite is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + for more details. + + You should have received a copy of the GNU Lesser General Public License + along with fuzzylite. If not, see <http://www.gnu.org/licenses/>. + + fuzzylite™ is a trademark of FuzzyLite Limited. + + */ + +#include "fl/rule/Rule.h" + +#include "fl/Exception.h" +#include "fl/hedge/Hedge.h" +#include "fl/imex/FllExporter.h" +#include "fl/norm/Norm.h" +#include "fl/rule/Antecedent.h" +#include "fl/rule/Consequent.h" + +#include <sstream> +#include <vector> + +namespace fl { + + Rule::Rule(const std::string& text, scalar weight) + : _text(text), _weight(weight), _antecedent(new Antecedent), _consequent(new Consequent) { + } + + Rule::Rule(const Rule& other) : _text(other._text), _weight(other._weight), + _antecedent(new Antecedent), _consequent(new Consequent) { + } + + Rule& Rule::operator=(const Rule& other) { + if (this != &other) { + unload(); + + _text = other._text; + _weight = other._weight; + _antecedent.reset(new Antecedent); + _consequent.reset(new Consequent); + } + return *this; + } + + Rule::~Rule() { + unload(); + } + + void Rule::setText(const std::string& text) { + this->_text = text; + } + + std::string Rule::getText() const { + return this->_text; + } + + void Rule::setWeight(scalar weight) { + this->_weight = weight; + } + + scalar Rule::getWeight() const { + return this->_weight; + } + + void Rule::setAntecedent(Antecedent* antecedent) { + this->_antecedent.reset(antecedent); + } + + Antecedent* Rule::getAntecedent() const { + return this->_antecedent.get(); + } + + void Rule::setConsequent(Consequent* consequent) { + this->_consequent.reset(consequent); + } + + Consequent* Rule::getConsequent() const { + return this->_consequent.get(); + } + + /** + * Operations for std::vector _hedges + */ + void Rule::addHedge(Hedge* hedge) { + this->_hedges[hedge->name()] = hedge; + } + + Hedge* Rule::getHedge(const std::string& name) const { + std::map<std::string, Hedge*>::const_iterator it = this->_hedges.find(name); + if (it != this->_hedges.end()) { + if (it->second) return it->second; + } + return fl::null; + } + + Hedge* Rule::removeHedge(const std::string& name) { + Hedge* result = fl::null; + std::map<std::string, Hedge*>::iterator it = this->_hedges.find(name); + if (it != this->_hedges.end()) { + result = it->second; + this->_hedges.erase(it); + } + return result; + } + + bool Rule::hasHedge(const std::string& name) const { + std::map<std::string, Hedge*>::const_iterator it = this->_hedges.find(name); + return (it != this->_hedges.end()); + } + + int Rule::numberOfHedges() const { + return this->_hedges.size(); + } + + void Rule::setHedges(const std::map<std::string, Hedge*>& hedges) { + this->_hedges = hedges; + } + + const std::map<std::string, Hedge*>& Rule::hedges() const { + return this->_hedges; + } + + std::map<std::string, Hedge*>& Rule::hedges() { + return this->_hedges; + } + + scalar Rule::activationDegree(const TNorm* conjunction, const SNorm* disjunction) const { + if (not isLoaded()) { + throw fl::Exception("[rule error] the following rule is not loaded: " + _text, FL_AT); + } + return _weight * getAntecedent()->activationDegree(conjunction, disjunction); + } + + void Rule::activate(scalar degree, const TNorm* activation) const { + if (not isLoaded()) { + throw fl::Exception("[rule error] the following rule is not loaded: " + _text, FL_AT); + } + _consequent->modify(degree, activation); + } + + bool Rule::isLoaded() const { + return _antecedent->isLoaded() and _consequent->isLoaded(); + } + + void Rule::unload() { + _antecedent->unload(); + _consequent->unload(); + + for (std::map<std::string, Hedge*>::const_iterator it = _hedges.begin(); + it != _hedges.end(); ++it) { + delete it->second; + } + _hedges.clear(); + } + + void Rule::load(const Engine* engine) { + load(_text, engine); + } + + void Rule::load(const std::string& rule, const Engine* engine) { + this->_text = rule; + std::istringstream tokenizer(rule.substr(0, rule.find_first_of('#'))); + std::string token; + std::ostringstream ossAntecedent, ossConsequent; + scalar weight = 1.0; + + enum FSM { + S_NONE, S_IF, S_THEN, S_WITH, S_END + }; + FSM state = S_NONE; + try { + while (tokenizer >> token) { + + switch (state) { + case S_NONE: + if (token == Rule::ifKeyword()) state = S_IF; + else { + std::ostringstream ex; + ex << "[syntax error] expected keyword <" << Rule::ifKeyword() << + ">, but found <" << token << "> in rule: " << rule; + throw fl::Exception(ex.str(), FL_AT); + } + break; + case S_IF: + if (token == Rule::thenKeyword()) state = S_THEN; + else ossAntecedent << token << " "; + break; + case S_THEN: + if (token == Rule::withKeyword()) state = S_WITH; + else ossConsequent << token << " "; + break; + case S_WITH: + try { + weight = fl::Op::toScalar(token); + state = S_END; + } catch (fl::Exception& e) { + std::ostringstream ex; + ex << "[syntax error] expected a numeric value as the weight of the rule: " + << rule; + e.append(ex.str(), FL_AT); + throw e; + } + break; + case S_END: + std::ostringstream ex; + ex << "[syntax error] unexpected token <" << token << "> at the end of rule"; + throw fl::Exception(ex.str(), FL_AT); + } + } + if (state == S_NONE) { + std::ostringstream ex; + ex << "[syntax error] " << (rule.empty() ? "empty rule" : "ignored rule: " + rule); + throw fl::Exception(ex.str(), FL_AT); + } else if (state == S_IF) { + std::ostringstream ex; + ex << "[syntax error] keyword <" << Rule::thenKeyword() << "> not found in rule: " << rule; + throw fl::Exception(ex.str(), FL_AT); + } else if (state == S_WITH) { + std::ostringstream ex; + ex << "[syntax error] expected a numeric value as the weight of the rule: " << rule; + throw fl::Exception(ex.str(), FL_AT); + } + + _antecedent->load(ossAntecedent.str(), this, engine); + _consequent->load(ossConsequent.str(), this, engine); + _weight = weight; + + } catch (...) { + unload(); + throw; + } + } + + std::string Rule::toString() const { + return FllExporter().toString(this); + } + + Rule* Rule::clone() const { + return new Rule(*this); + } + + Rule* Rule::parse(const std::string& rule, const Engine* engine) { + FL_unique_ptr<Rule> result(new Rule); + result->load(rule, engine); + return result.release(); + } + +} diff --git a/fuzzylite/src/rule/RuleBlock.cpp b/fuzzylite/src/rule/RuleBlock.cpp new file mode 100644 index 0000000..9ab813e --- /dev/null +++ b/fuzzylite/src/rule/RuleBlock.cpp @@ -0,0 +1,210 @@ +/* + Author: Juan Rada-Vilela, Ph.D. + Copyright (C) 2010-2014 FuzzyLite Limited + All rights reserved + + This file is part of fuzzylite. + + fuzzylite is free software: you can redistribute it and/or modify it under + the terms of the GNU Lesser General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + fuzzylite is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + for more details. + + You should have received a copy of the GNU Lesser General Public License + along with fuzzylite. If not, see <http://www.gnu.org/licenses/>. + + fuzzylite™ is a trademark of FuzzyLite Limited. + + */ + +#include "fl/rule/RuleBlock.h" + +#include "fl/imex/FllExporter.h" +#include "fl/norm/TNorm.h" +#include "fl/norm/SNorm.h" +#include "fl/rule/Rule.h" + +#include <sstream> + +namespace fl { + + RuleBlock::RuleBlock(const std::string& name) + : _name(name), _enabled(true) { + } + + RuleBlock::RuleBlock(const RuleBlock& other) : _name(other._name), + _enabled(true) { + copyFrom(other); + } + + RuleBlock& RuleBlock::operator=(const RuleBlock& other) { + if (this != &other) { + for (std::size_t i = 0; i < _rules.size(); ++i) { + delete _rules.at(i); + } + _rules.clear(); + _conjunction.reset(fl::null); + _disjunction.reset(fl::null); + _activation.reset(fl::null); + + copyFrom(other); + } + return *this; + } + + void RuleBlock::copyFrom(const RuleBlock& source) { + _name = source._name; + _enabled = source._enabled; + if (source._activation.get()) _activation.reset(source._activation->clone()); + if (source._conjunction.get()) _conjunction.reset(source._conjunction->clone()); + if (source._disjunction.get()) _disjunction.reset(source._disjunction->clone()); + for (std::size_t i = 0; i < source._rules.size(); ++i) { + _rules.push_back(source._rules.at(i)->clone()); + } + } + + RuleBlock::~RuleBlock() { + for (std::size_t i = 0; i < _rules.size(); ++i) { + delete _rules.at(i); + } + _rules.clear(); + } + + void RuleBlock::activate() { + FL_DBG("==================="); + FL_DBG("ACTIVATING RULEBLOCK " << _name); + for (std::size_t i = 0; i < _rules.size(); ++i) { + Rule* rule = _rules.at(i); + if (rule->isLoaded()) { + scalar activationDegree = rule->activationDegree(_conjunction.get(), _disjunction.get()); + FL_DBG("[degree=" << Op::str(activationDegree) << "] " << rule->toString()); + if (Op::isGt(activationDegree, 0.0)) { + rule->activate(activationDegree, _activation.get()); + } + } else { + FL_DBG("Rule not loaded: " << rule->toString()); + } + } + } + + void RuleBlock::unloadRules() const { + for (std::size_t i = 0; i < _rules.size(); ++i) { + _rules.at(i)->unload(); + } + } + + void RuleBlock::loadRules(const Engine* engine) { + std::ostringstream exceptions; + bool throwException = false; + for (std::size_t i = 0; i < _rules.size(); ++i) { + Rule* rule = _rules.at(i); + if (rule->isLoaded()) { + rule->unload(); + } + try { + rule->load(engine); + } catch (std::exception& ex) { + throwException = true; + exceptions << ex.what() << "\n"; + } + } + if (throwException) { + fl::Exception exception("[ruleblock error] the following " + "rules could not be loaded:\n" + exceptions.str(), FL_AT); + throw exception; + } + } + + void RuleBlock::reloadRules(const Engine* engine) { + unloadRules(); + loadRules(engine); + } + + void RuleBlock::setName(std::string name) { + this->_name = name; + } + + std::string RuleBlock::getName() const { + return this->_name; + } + + void RuleBlock::setConjunction(TNorm* tnorm) { + this->_conjunction.reset(tnorm); + } + + TNorm* RuleBlock::getConjunction() const { + return this->_conjunction.get(); + } + + void RuleBlock::setDisjunction(SNorm* snorm) { + this->_disjunction.reset(snorm); + } + + SNorm* RuleBlock::getDisjunction() const { + return this->_disjunction.get(); + } + + void RuleBlock::setActivation(TNorm* activation) { + this->_activation.reset(activation); + } + + TNorm* RuleBlock::getActivation() const { + return this->_activation.get(); + } + + void RuleBlock::setEnabled(bool enabled) { + this->_enabled = enabled; + } + + bool RuleBlock::isEnabled() const { + return this->_enabled; + } + + std::string RuleBlock::toString() const { + return FllExporter().toString(this); + } + + /** + * Operations for std::vector _rules + */ + void RuleBlock::addRule(Rule* rule) { + this->_rules.push_back(rule); + } + + void RuleBlock::insertRule(Rule* rule, int index) { + this->_rules.insert(this->_rules.begin() + index, rule); + } + + Rule* RuleBlock::getRule(int index) const { + return this->_rules.at(index); + } + + Rule* RuleBlock::removeRule(int index) { + Rule* result = this->_rules.at(index); + this->_rules.erase(this->_rules.begin() + index); + return result; + } + + int RuleBlock::numberOfRules() const { + return this->_rules.size(); + } + + const std::vector<Rule*>& RuleBlock::rules() const { + return this->_rules; + } + + void RuleBlock::setRules(const std::vector<Rule*>& rules) { + this->_rules = rules; + } + + std::vector<Rule*>& RuleBlock::rules() { + return this->_rules; + } + + +} |