Merge branch 'master' into 3-parse-rule

# Conflicts:
#	src/main/java/ru/ulstu/fc/rule/model/Variable.java
#	src/main/java/ru/ulstu/fc/rule/service/FuzzyInferenceService.java
#	src/main/resources/templates/default.html
This commit is contained in:
Anton Romanov 2023-11-06 19:21:39 +04:00
commit 24a59f204d
11 changed files with 226 additions and 61 deletions

View File

@ -59,7 +59,6 @@ dependencies {
implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-hibernate5'
implementation group: 'com.h2database', name:'h2'
implementation group: 'commons-io', name: 'commons-io', version: '2.6'
implementation group: 'net.sourceforge.htmlunit', name: 'htmlunit', version: '2.35.0'
implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.12.0'
implementation group: 'io.springfox', name: 'springfox-boot-starter', version: '3.0.0'

View File

@ -14,6 +14,9 @@ import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.CookieLocaleResolver;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
@Configuration
public class MvcConfiguration implements WebMvcConfigurer {
@ -41,6 +44,14 @@ public class MvcConfiguration implements WebMvcConfigurer {
return localeInterceptor;
}
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("ru.ulstu"))
.build();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(localeInterceptor());

View File

@ -9,11 +9,14 @@ import org.springframework.web.bind.annotation.RequestMethod;
import ru.ulstu.fc.rule.model.Antecedent;
import ru.ulstu.fc.rule.model.InferenceForm;
import ru.ulstu.fc.rule.service.FuzzyInferenceService;
import springfox.documentation.annotations.ApiIgnore;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@Controller
@ApiIgnore
public class InferenceMvcController {
private final FuzzyInferenceService fuzzyInferenceService;
@ -34,21 +37,24 @@ public class InferenceMvcController {
model.addAttribute("ageAntecedents", getAgeAntecedents());
model.addAttribute("incomeAntecedents", getIncomeAntecedents());
model.addAttribute("inferenceForm", inferenceForm);
model.addAttribute("response", fuzzyInferenceService.getFuzzyInference());
model.addAttribute("response", fuzzyInferenceService.getFuzzyInference(
Map.of("возраст", Double.valueOf(inferenceForm.getAgeAntecedent()),
"доход", Double.valueOf(inferenceForm.getIncomeAntecedent())
)));
return "index";
}
private List<Antecedent> getAgeAntecedents() {
return Arrays.asList(
new Antecedent("молодой", "young"),
new Antecedent("средний", "average"),
new Antecedent("старый", "old"));
new Antecedent("молодой", "30"),
new Antecedent("средний", "45"),
new Antecedent("старый", "60"));
}
private List<Antecedent> getIncomeAntecedents() {
return Arrays.asList(
new Antecedent("малый", "small"),
new Antecedent("средний", "average"),
new Antecedent("высокий", "high"));
new Antecedent("небольшой", "20000"),
new Antecedent("средний", "90000"),
new Antecedent("высокий", "200000"));
}
}

View File

@ -0,0 +1,29 @@
package ru.ulstu.fc.rule.controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import ru.ulstu.fc.rule.model.InferenceData;
import ru.ulstu.fc.rule.model.OutputValue;
import ru.ulstu.fc.rule.service.FuzzyInferenceService;
import java.util.List;
@RestController
@RequestMapping("rest")
public class InferenceRestController {
private final FuzzyInferenceService fuzzyInferenceService;
public InferenceRestController(FuzzyInferenceService fuzzyInferenceService) {
this.fuzzyInferenceService = fuzzyInferenceService;
}
@RequestMapping(value = "get-inference", method = RequestMethod.POST)
public List<OutputValue> getInference(@RequestBody InferenceData inferenceData) {
return fuzzyInferenceService.getFuzzyInference(inferenceData.getRules(),
inferenceData.getValues(),
inferenceData.getInputVariables(),
inferenceData.getOutputVariable());
}
}

View File

@ -0,0 +1,43 @@
package ru.ulstu.fc.rule.model;
import java.util.List;
import java.util.Map;
public class InferenceData {
private List<String> rules;
private Map<String, Double> values;
private List<Variable> inputVariables;
private Variable outputVariable;
public List<String> getRules() {
return rules;
}
public void setRules(List<String> rules) {
this.rules = rules;
}
public Map<String, Double> getValues() {
return values;
}
public void setValues(Map<String, Double> values) {
this.values = values;
}
public List<Variable> getInputVariables() {
return inputVariables;
}
public void setInputVariables(List<Variable> inputVariables) {
this.inputVariables = inputVariables;
}
public Variable getOutputVariable() {
return outputVariable;
}
public void setOutputVariable(Variable outputVariable) {
this.outputVariable = outputVariable;
}
}

View File

@ -0,0 +1,27 @@
package ru.ulstu.fc.rule.model;
public class OutputValue {
private String fuzzyTerm;
private Double degree;
public OutputValue(String fuzzyTerm, Double degree) {
this.fuzzyTerm = fuzzyTerm;
this.degree = degree;
}
public String getFuzzyTerm() {
return fuzzyTerm;
}
public void setFuzzyTerm(String fuzzyTerm) {
this.fuzzyTerm = fuzzyTerm;
}
public Double getDegree() {
return degree;
}
public void setDegree(Double degree) {
this.degree = degree;
}
}

View File

@ -0,0 +1,30 @@
package ru.ulstu.fc.rule.model;
public class VariableValue {
private String fuzzyTerm;
private Double value;
public VariableValue() {
}
public VariableValue(String fuzzyTerm, Double value) {
this.fuzzyTerm = fuzzyTerm;
this.value = value;
}
public String getFuzzyTerm() {
return fuzzyTerm;
}
public void setFuzzyTerm(String fuzzyTerm) {
this.fuzzyTerm = fuzzyTerm;
}
public Double getValue() {
return value;
}
public void setValue(Double value) {
this.value = value;
}
}

View File

@ -2,24 +2,24 @@ package ru.ulstu.fc.rule.service;
import com.fuzzylite.Engine;
import com.fuzzylite.activation.General;
import com.fuzzylite.defuzzifier.Centroid;
import com.fuzzylite.defuzzifier.WeightedAverage;
import com.fuzzylite.norm.s.Maximum;
import com.fuzzylite.norm.t.AlgebraicProduct;
import com.fuzzylite.norm.t.Minimum;
import com.fuzzylite.rule.Rule;
import com.fuzzylite.rule.RuleBlock;
import com.fuzzylite.term.Activated;
import com.fuzzylite.term.Triangle;
import com.fuzzylite.variable.InputVariable;
import com.fuzzylite.variable.OutputVariable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import ru.ulstu.fc.rule.model.OutputValue;
import ru.ulstu.fc.rule.model.Variable;
import ru.ulstu.fc.rule.model.VariableValue;
import java.util.AbstractMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
@Service
@ -32,69 +32,52 @@ public class FuzzyInferenceService {
private final static String NO_RESULT = "Нет результата";
private final Engine fuzzyEngine;
private Map<String, List<Entry<String, Integer>>> inputFuzzyTerms = Map.of(
"возраст",
List.of(
new AbstractMap.SimpleEntry("молодой", 35),
new AbstractMap.SimpleEntry("средний", 60),
new AbstractMap.SimpleEntry("старый", 100)),
"доход",
List.of(
new AbstractMap.SimpleEntry("небольшой", 35000),
new AbstractMap.SimpleEntry("средний", 100000),
new AbstractMap.SimpleEntry("высокий", 500000)));
private Map<String, List<Entry<String, Integer>>> outputFuzzyTerms = Map.of(
"кредит", List.of(new AbstractMap.SimpleEntry("небольшой", 20000),
new AbstractMap.SimpleEntry("средний", 100000),
new AbstractMap.SimpleEntry("большой", 1000000)));
public FuzzyInferenceService(Engine fuzzyEngine) {
this.fuzzyEngine = fuzzyEngine;
}
private List<String> getDemoRules() {
return List.of(
String.format(RULE_TEMPLATE, "возраст", "молодой", "доход", "высокий", "большой"),
String.format(RULE_TEMPLATE, "возраст", "средний", "доход", "высокий", "средний"),
String.format(RULE_TEMPLATE, "возраст", "старый", "доход", "высокий", "средний")
String.format(RULE_TEMPLATE, "возраст", "старый", "доход", "высокий", "средний"),
String.format(RULE_TEMPLATE, "возраст", "старый", "доход", "небольшой", "небольшой"),
String.format(RULE_TEMPLATE, "возраст", "молодой", "доход", "небольшой", "небольшой")
);
}
private List<InputVariable> getInputVariables() {
return List.of(getInputVariable("возраст", inputFuzzyTerms.get("возраст")),
getInputVariable("доход", inputFuzzyTerms.get("доход")));
}
private InputVariable getInputVariable(String name, List<Entry<String, Integer>> terms) {
private InputVariable getInputVariable(Variable variable) {
final InputVariable input = new InputVariable();
input.setName(name);
input.setName(variable.getName());
input.setDescription("");
input.setRange(0, terms.get(terms.size() - 1).getValue());
input.setRange(0, variable.getValues().get(variable.getValues().size() - 1).getValue());
input.setEnabled(true);
input.setLockValueInRange(false);
double prev = 0;
for (int i = 0; i < terms.size(); i++) {
Triangle term = new Triangle(terms.get(i).getKey(), prev, terms.get(i).getValue());
for (int i = 0; i < variable.getValues().size(); i++) {
Triangle term = new Triangle(variable.getValues().get(i).getFuzzyTerm(),
prev,
variable.getValues().get(i).getValue(),
variable.getValues().get(i).getValue() + variable.getValues().get(i).getValue() - prev);
prev = term.getVertexB();
input.addTerm(term);
}
return input;
}
private <T extends Enum<T>> OutputVariable getOutputVariable(String name, List<Entry<String, Integer>> terms) {
private <T extends Enum<T>> OutputVariable getOutputVariable(Variable variable) {
final OutputVariable output = new OutputVariable();
output.setName(name);
output.setName(variable.getName());
output.setDescription("");
output.setRange(0, terms.get(terms.size() - 1).getValue());
output.setRange(0, variable.getValues().get(variable.getValues().size() - 1).getValue());
output.setEnabled(true);
output.setAggregation(new Maximum());
output.setDefuzzifier(new Centroid(terms.get(terms.size() - 1).getValue()));
output.setDefuzzifier(new WeightedAverage());
output.setDefaultValue(Double.NaN);
output.setLockValueInRange(false);
double prev = 0;
for (int i = 0; i < terms.size(); i++) {
Triangle term = new Triangle(terms.get(i).getKey(), prev, terms.get(i).getValue());
for (int i = 0; i < variable.getValues().size(); i++) {
Triangle term = new Triangle(
variable.getValues().get(i).getFuzzyTerm(),
prev,
variable.getValues().get(i).getValue(),
variable.getValues().get(i).getValue() + variable.getValues().get(i).getValue() - prev);
prev = term.getVertexB();
output.addTerm(term);
}
@ -102,9 +85,11 @@ public class FuzzyInferenceService {
}
private RuleBlock getRuleBlock(Engine engine,
List<String> rules) {
getInputVariables().forEach(engine::addInputVariable);
engine.addOutputVariable(getOutputVariable("кредит", outputFuzzyTerms.get("кредит")));
List<String> rules,
List<Variable> inputVariables,
Variable outputVariable) {
inputVariables.stream().map(this::getInputVariable).forEach(engine::addInputVariable);
engine.addOutputVariable(getOutputVariable(outputVariable));
RuleBlock mamdani = new RuleBlock();
mamdani.setName("mamdani");
@ -117,7 +102,7 @@ public class FuzzyInferenceService {
return mamdani;
}
private Map<String, Double> getConsequent(Engine engine, Map<String, Double> variableValues) {
private List<OutputValue> getConsequent(Engine engine, Map<String, Double> variableValues) {
OutputVariable outputVariable = engine.getOutputVariable(OUTPUT_VARIABLE_NAME);
for (Map.Entry<String, Double> variableValue : variableValues.entrySet()) {
InputVariable inputVariable = engine.getInputVariable(variableValue.getKey());
@ -128,14 +113,39 @@ public class FuzzyInferenceService {
LOG.info("Output: {}", outputVariable.getValue());
}
return Double.isNaN(outputVariable.getValue())
? Map.of(NO_RESULT, 0.0)
: outputVariable.fuzzyOutput().getTerms().stream().collect(Collectors.toMap(t -> t.getTerm().getName(), Activated::getDegree));
? List.of(new OutputValue(NO_RESULT, 0.0))
: outputVariable.fuzzyOutput()
.getTerms()
.stream()
.map(t -> new OutputValue(t.getTerm().getName(), t.getDegree()))
.collect(Collectors.toList());
}
public Map<String, Double> getFuzzyInference() {
public List<OutputValue> getFuzzyInference(Map<String, Double> vals) {
return getFuzzyInference(getDemoRules(), vals,
List.of(new Variable("возраст", List.of(
new VariableValue("молодой", 35.0),
new VariableValue("средний", 60.0),
new VariableValue("старый", 100.0))
),
new Variable("доход", List.of(
new VariableValue("небольшой", 35000.0),
new VariableValue("средний", 100000.0),
new VariableValue("высокий", 500000.0))
)
),
new Variable("кредит", List.of(
new VariableValue("небольшой", 20000.0),
new VariableValue("средний", 100000.0),
new VariableValue("большой", 1000000.0)))
);
}
public List<OutputValue> getFuzzyInference(List<String> rules,
Map<String, Double> values,
List<Variable> inputVariables,
Variable outputVariable) {
fuzzyEngine.getRuleBlocks().clear();
fuzzyEngine.addRuleBlock(getRuleBlock(fuzzyEngine, getDemoRules()));
return getConsequent(fuzzyEngine, Map.of("возраст", 20.0, "доход", 250000.0));
fuzzyEngine.addRuleBlock(getRuleBlock(fuzzyEngine, getDemoRules())); return getConsequent(engine, values);
}
}

View File

@ -18,4 +18,6 @@ spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.h2.console.enabled=true
spring.jpa.hibernate.ddl-auto=update
# swagger-ui custom path
springdoc.swagger-ui.path=/swagger-ui.html

View File

@ -30,6 +30,9 @@
<li class="nav-item">
<a class="nav-link" href="/listRules">Правила</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/swagger-ui/index.html">API</a>
</li>
</ul>
</div>
</nav>

View File

@ -50,7 +50,12 @@
</div>
</div>
<input type="submit" class="btn btn-outline-success m-2" value="Получить результат вывода"/>
<div class="row" th:text="${response}"></div>
<div class="row" th:each="out : ${response}">
<div class="col-md-2"> Размер кредита:</div>
<div class="col-md-4" th:text="${out.fuzzyTerm}"></div>
<div class="col-md-3"> Степень принадлежности:</div>
<div class="col-md-1" th:text="${out.degree}"></div>
</div>
</form>
</div>
</html>