LSR/tests/test_controlsystem.py

425 lines
20 KiB
Python
Raw Permalink Normal View History

2020-06-04 19:21:01 +02:00
from __future__ import division
import sys
import numpy as np
import numpy.testing as tst
import networkx
import nose
import skfuzzy as fuzz
import skfuzzy.control as ctrl
def test_tipping_problem():
# The full tipping problem uses many of these methods
food = ctrl.Antecedent(np.linspace(0, 10, 11), 'quality')
service = ctrl.Antecedent(np.linspace(0, 10, 11), 'service')
tip = ctrl.Consequent(np.linspace(0, 25, 26), 'tip')
food.automf(3)
service.automf(3)
# Manual membership function definition
tip['bad'] = fuzz.trimf(tip.universe, [0, 0, 13])
tip['middling'] = fuzz.trimf(tip.universe, [0, 13, 25])
tip['lots'] = fuzz.trimf(tip.universe, [13, 25, 25])
# Define fuzzy rules
rule1 = ctrl.Rule(food['poor'] | service['poor'], tip['bad'])
rule2 = ctrl.Rule(service['average'], tip['middling'])
rule3 = ctrl.Rule(service['good'] | food['good'], tip['lots'])
# The control system - defined both possible ways
tipping = ctrl.ControlSystem([rule1, rule2, rule3])
tipping2 = ctrl.ControlSystem(rule1)
tipping2.addrule(rule2)
tipping2.addrule(rule3)
tip_sim = ctrl.ControlSystemSimulation(tipping)
tip_sim2 = ctrl.ControlSystemSimulation(tipping2)
# Inputs added both possible ways
inputs = {'quality': 6.5, 'service': 9.8}
for key, value in inputs.items():
tip_sim.input[key] = value
tip_sim2.inputs(inputs)
# Compute the system
tip_sim.compute()
tip_sim2.compute()
# Ensure both methods of defining rules yield the same results
for val0, val1 in zip(tip_sim.output.values(),
tip_sim2.output.values()):
tst.assert_allclose(val0, val1)
# Verify against manual computation
tst.assert_allclose(tip_sim.output['tip'], 19.8578, atol=1e-2, rtol=1e-2)
def setup_rule_order():
global a, b, c, d
a = ctrl.Antecedent(np.linspace(0, 10, 11), 'a')
b = ctrl.Antecedent(np.linspace(0, 10, 11), 'b')
c = ctrl.Antecedent(np.linspace(0, 10, 11), 'c')
d = ctrl.Antecedent(np.linspace(0, 10, 11), 'd')
for v in (a, b, c, d):
v.automf(3)
@tst.decorators.skipif(float(networkx.__version__) >= 2.0)
@nose.with_setup(setup_rule_order)
def test_rule_order():
# Make sure rules are exposed in the order needed to solve them
# correctly
global a, b, c, d
r1 = ctrl.Rule(a['average'] | a['poor'], c['poor'], label='r1')
r2 = ctrl.Rule(c['poor'] | b['poor'], c['good'], label='r2')
r3 = ctrl.Rule(c['good'] | a['good'], d['good'], label='r3')
ctrl_sys = ctrl.ControlSystem([r1, r2, r3])
resolved = [r for r in ctrl_sys.rules]
assert resolved == [r1, r2, r3], "Order given was: {0}, expected {1}".format(
resolved, [r1.label, r2.label, r3.label])
# The assert_raises decorator does not work in Python 2.6
@tst.decorators.skipif(
(sys.version_info < (2, 7)) or (float(networkx.__version__) >= 2.0))
@nose.with_setup(setup_rule_order)
def test_unresolvable_rule_order():
# Make sure we don't get suck in an infinite loop when the user
# gives an unresolvable rule order
global a, b, c, d
r1 = ctrl.Rule(a['average'] | a['poor'], c['poor'], label='r1')
r2 = ctrl.Rule(c['poor'] | b['poor'], c['poor'], label='r2')
r3 = ctrl.Rule(c['good'] | a['good'], d['good'], label='r3')
ex_msg = "Unable to resolve rule execution order"
with tst.assert_raises(RuntimeError, expected_regexp=ex_msg):
ctrl_sys = ctrl.ControlSystem([r1, r2, r3])
list(ctrl_sys.rules)
@nose.with_setup(setup_rule_order)
def test_bad_rules():
not_rules = ['me', 192238, 42, dict()]
tst.assert_raises(ValueError, ctrl.ControlSystem, not_rules)
testsystem = ctrl.ControlSystem()
tst.assert_raises(ValueError, testsystem.addrule, a)
def test_multiple_rules_same_consequent_term():
# 2 input variables, 1 output variable and 7 instances.
x1_inputs = [0.6, 0.2, 0.4, 0.7, 1, 1.2, 1.8]
x2_inputs = [0.9, 1, 0.8, 0, 1.2, 0.6, 1.8]
dom = np.arange(0, 2.1, 0.01)
x1 = ctrl.Antecedent(dom, "x1")
x1['label0'] = fuzz.trimf(x1.universe, (0.2, 0.2, 0.6))
x1['label1'] = fuzz.trimf(x1.universe, (0.2, 0.6, 1.0))
x1['label2'] = fuzz.trimf(x1.universe, (0.6, 1.0, 1.4))
x1['label3'] = fuzz.trimf(x1.universe, (1.0, 1.4, 1.8))
x1['label4'] = fuzz.trimf(x1.universe, (1.4, 1.8, 1.8))
x2 = ctrl.Antecedent(dom, "x2")
x2['label0'] = fuzz.trimf(x2.universe, (0.0, 0.0, 0.45))
x2['label1'] = fuzz.trimf(x2.universe, (0.0, 0.45, 0.9))
x2['label2'] = fuzz.trimf(x2.universe, (0.45, 0.9, 1.35))
x2['label3'] = fuzz.trimf(x2.universe, (0.9, 1.35, 1.8))
x2['label4'] = fuzz.trimf(x2.universe, (1.35, 1.8, 1.8))
y = ctrl.Consequent(dom, "y")
y['label0'] = fuzz.trimf(y.universe, (0.3, 0.3, 0.725))
y['label1'] = fuzz.trimf(y.universe, (0.3, 0.725, 1.15))
y['label2'] = fuzz.trimf(y.universe, (0.725, 1.15, 1.575))
y['label3'] = fuzz.trimf(y.universe, (1.15, 1.575, 2.0))
y['label4'] = fuzz.trimf(y.universe, (1.575, 2.0, 2.0))
r1 = ctrl.Rule(x1['label0'] & x2['label2'], y['label0'])
r2 = ctrl.Rule(x1['label1'] & x2['label0'], y['label0'])
r3 = ctrl.Rule(x1['label1'] & x2['label2'], y['label0'])
# Equivalent to above 3 rules
r123 = ctrl.Rule((x1['label0'] & x2['label2']) |
(x1['label1'] & x2['label0']) |
(x1['label1'] & x2['label2']), y['label0'])
r4 = ctrl.Rule(x1['label2'] & x2['label1'], y['label2'])
r5 = ctrl.Rule(x1['label2'] & x2['label3'], y['label3'])
r6 = ctrl.Rule(x1['label4'] & x2['label4'], y['label4'])
# Build a system with three rules targeting the same Consequent Term,
# and then an equivalent system with those three rules combined into one.
cs0 = ctrl.ControlSystem([r1, r2, r3, r4, r5, r6])
cs1 = ctrl.ControlSystem([r123, r4, r5, r6])
expected_results = [0.438372093023,
0.443962536855,
0.461436409933,
0.445290345769,
1.575,
1.15,
1.86162790698]
# Ensure the results are equivalent within error
for inst, expected in zip(range(7), expected_results):
sim0 = ctrl.ControlSystemSimulation(cs0)
sim1 = ctrl.ControlSystemSimulation(cs1)
sim0.input["x1"] = x1_inputs[inst]
sim0.input["x2"] = x2_inputs[inst]
sim1.input["x1"] = x1_inputs[inst]
sim1.input["x2"] = x2_inputs[inst]
sim0.compute()
sim1.compute()
tst.assert_allclose(sim0.output['y'], sim1.output['y'])
tst.assert_allclose(expected, sim0.output['y'], atol=1e-4, rtol=1e-4)
def test_complex_system():
# A much more complex system, run multiple times & with array inputs
universe = np.linspace(-2, 2, 5)
error = ctrl.Antecedent(universe, 'error')
delta = ctrl.Antecedent(universe, 'delta')
output = ctrl.Consequent(universe, 'output')
names = ['nb', 'ns', 'ze', 'ps', 'pb']
error.automf(names=names)
delta.automf(names=names)
output.automf(names=names)
# The rulebase:
# rule 1: IF e = ZE AND delta = ZE THEN output = ZE
# rule 2: IF e = ZE AND delta = SP THEN output = SN
# rule 3: IF e = SN AND delta = SN THEN output = LP
# rule 4: IF e = LP OR delta = LP THEN output = LN
rule0 = ctrl.Rule(antecedent=((error['nb'] & delta['nb']) | # This combination, or...
(error['ns'] & delta['nb']) |
(error['nb'] & delta['ns'])),
consequent=output['nb'], label='rule nb')
rule1 = ctrl.Rule(antecedent=((error['nb'] & delta['ze']) |
(error['nb'] & delta['ps']) |
(error['ns'] & delta['ns']) |
(error['ns'] & delta['ze']) |
(error['ze'] & delta['ns']) |
(error['ze'] & delta['nb']) |
(error['ps'] & delta['nb'])),
consequent=output['ns'], label='rule ns')
rule2 = ctrl.Rule(antecedent=((error['nb'] & delta['pb']) |
(error['ns'] & delta['ps']) |
(error['ze'] & delta['ze']) |
(error['ps'] & delta['ns']) |
(error['pb'] & delta['nb'])),
consequent=output['ze'], label='rule ze')
rule3 = ctrl.Rule(antecedent=((error['ns'] & delta['pb']) |
(error['ze'] & delta['pb']) |
(error['ze'] & delta['ps']) |
(error['ps'] & delta['ps']) |
(error['ps'] & delta['ze']) |
(error['pb'] & delta['ze']) |
(error['pb'] & delta['ns'])),
consequent=output['ps'], label='rule ps')
rule4 = ctrl.Rule(antecedent=((error['ps'] & delta['pb']) |
(error['pb'] & delta['pb']) |
(error['pb'] & delta['ps'])),
consequent=output['pb'], label='rule pb')
system = ctrl.ControlSystem(rules=[rule0, rule1, rule2, rule3, rule4])
sim = ctrl.ControlSystemSimulation(system, cache=False)
x, y = np.meshgrid(np.linspace(-2, 2, 21), np.linspace(-2, 2, 21))
z0 = np.zeros_like(x)
z1 = np.zeros_like(x)
# The original, slow way - one set of values at a time
for i in range(21):
for j in range(21):
sim.input['error'] = x[i, j]
sim.input['delta'] = y[i, j]
sim.compute()
z0[i, j] = sim.output['output']
sim.reset()
# The new way - array inputs
sim.input['error'] = x
sim.input['delta'] = y
sim.compute()
z1 = sim.output['output']
# Ensure results align
np.testing.assert_allclose(z0, z1)
# Big expected array
expected = \
np.array([[ -1.66666667e+00, -1.65555556e+00, -1.62857143e+00,
-1.62857143e+00, -1.65555556e+00, -1.66666667e+00,
-1.34414414e+00, -1.18294574e+00, -1.10000000e+00,
-1.05641026e+00, -1.00000000e+00, -1.00000000e+00,
-1.00000000e+00, -1.00000000e+00, -1.00000000e+00,
-1.00000000e+00, -7.37704918e-01, -5.72916667e-01,
-4.27083333e-01, -2.62295082e-01, -2.77555756e-17],
[ -1.65555556e+00, -1.34414414e+00, -1.29494949e+00,
-1.29494949e+00, -1.34414414e+00, -1.34414414e+00,
-1.34414414e+00, -1.18294574e+00, -1.10000000e+00,
-1.05641026e+00, -1.00000000e+00, -7.37704918e-01,
-7.13580247e-01, -7.13580247e-01, -7.37704918e-01,
-7.37704918e-01, -4.36619718e-01, -2.91555556e-01,
-1.56140351e-01, 1.96914557e-16, 2.62295082e-01],
[ -1.62857143e+00, -1.29494949e+00, -1.18294574e+00,
-1.18294574e+00, -1.18294574e+00, -1.18294574e+00,
-1.18294574e+00, -1.18294574e+00, -1.10000000e+00,
-1.05333333e+00, -1.00000000e+00, -7.13580247e-01,
-5.72916667e-01, -5.72916667e-01, -5.72916667e-01,
-5.72916667e-01, -2.91555556e-01, -1.26984127e-01,
6.45478503e-17, 1.56140351e-01, 4.27083333e-01],
[ -1.62857143e+00, -1.29494949e+00, -1.18294574e+00,
-1.10000000e+00, -1.10000000e+00, -1.10000000e+00,
-1.10000000e+00, -1.10000000e+00, -1.10000000e+00,
-1.05333333e+00, -1.00000000e+00, -7.13580247e-01,
-5.72916667e-01, -4.27083333e-01, -4.27083333e-01,
-4.27083333e-01, -1.56140351e-01, 2.42054439e-16,
1.26984127e-01, 2.91555556e-01, 5.72916667e-01],
[ -1.65555556e+00, -1.34414414e+00, -1.18294574e+00,
-1.10000000e+00, -1.05641026e+00, -1.05641026e+00,
-1.05641026e+00, -1.05333333e+00, -1.05333333e+00,
-1.05641026e+00, -1.00000000e+00, -7.37704918e-01,
-5.72916667e-01, -4.27083333e-01, -2.62295082e-01,
-2.62295082e-01, 2.29733650e-16, 1.56140351e-01,
2.91555556e-01, 4.36619718e-01, 7.37704918e-01],
[ -1.66666667e+00, -1.34414414e+00, -1.18294574e+00,
-1.10000000e+00, -1.05641026e+00, -1.00000000e+00,
-1.00000000e+00, -1.00000000e+00, -1.00000000e+00,
-1.00000000e+00, -1.00000000e+00, -7.37704918e-01,
-5.72916667e-01, -4.27083333e-01, -2.62295082e-01,
-2.77555756e-17, 2.62295082e-01, 4.27083333e-01,
5.72916667e-01, 7.37704918e-01, 1.00000000e+00],
[ -1.34414414e+00, -1.34414414e+00, -1.18294574e+00,
-1.10000000e+00, -1.05641026e+00, -1.00000000e+00,
-7.37704918e-01, -7.13580247e-01, -7.13580247e-01,
-7.37704918e-01, -7.37704918e-01, -4.36619718e-01,
-2.91555556e-01, -1.56140351e-01, 4.17271323e-16,
2.62295082e-01, 2.62295082e-01, 4.27083333e-01,
5.72916667e-01, 7.37704918e-01, 1.00000000e+00],
[ -1.18294574e+00, -1.18294574e+00, -1.18294574e+00,
-1.10000000e+00, -1.05333333e+00, -1.00000000e+00,
-7.13580247e-01, -5.72916667e-01, -5.72916667e-01,
-5.72916667e-01, -5.72916667e-01, -2.91555556e-01,
-1.26984127e-01, 2.09780513e-16, 1.56140351e-01,
4.27083333e-01, 4.27083333e-01, 4.27083333e-01,
5.72916667e-01, 7.13580247e-01, 1.00000000e+00],
[ -1.10000000e+00, -1.10000000e+00, -1.10000000e+00,
-1.10000000e+00, -1.05333333e+00, -1.00000000e+00,
-7.13580247e-01, -5.72916667e-01, -4.27083333e-01,
-4.27083333e-01, -4.27083333e-01, -1.56140351e-01,
2.42054439e-16, 1.26984127e-01, 2.91555556e-01,
5.72916667e-01, 5.72916667e-01, 5.72916667e-01,
5.72916667e-01, 7.13580247e-01, 1.00000000e+00],
[ -1.05641026e+00, -1.05641026e+00, -1.05333333e+00,
-1.05333333e+00, -1.05641026e+00, -1.00000000e+00,
-7.37704918e-01, -5.72916667e-01, -4.27083333e-01,
-2.62295082e-01, -2.62295082e-01, 2.29733650e-16,
1.56140351e-01, 2.91555556e-01, 4.36619718e-01,
7.37704918e-01, 7.37704918e-01, 7.13580247e-01,
7.13580247e-01, 7.37704918e-01, 1.00000000e+00],
[ -1.00000000e+00, -1.00000000e+00, -1.00000000e+00,
-1.00000000e+00, -1.00000000e+00, -1.00000000e+00,
-7.37704918e-01, -5.72916667e-01, -4.27083333e-01,
-2.62295082e-01, -2.77555756e-17, 2.62295082e-01,
4.27083333e-01, 5.72916667e-01, 7.37704918e-01,
1.00000000e+00, 1.00000000e+00, 1.00000000e+00,
1.00000000e+00, 1.00000000e+00, 1.00000000e+00],
[ -1.00000000e+00, -7.37704918e-01, -7.13580247e-01,
-7.13580247e-01, -7.37704918e-01, -7.37704918e-01,
-4.36619718e-01, -2.91555556e-01, -1.56140351e-01,
2.29733650e-16, 2.62295082e-01, 2.62295082e-01,
4.27083333e-01, 5.72916667e-01, 7.37704918e-01,
1.00000000e+00, 1.05641026e+00, 1.05333333e+00,
1.05333333e+00, 1.05641026e+00, 1.05641026e+00],
[ -1.00000000e+00, -7.13580247e-01, -5.72916667e-01,
-5.72916667e-01, -5.72916667e-01, -5.72916667e-01,
-2.91555556e-01, -1.26984127e-01, 2.42054439e-16,
1.56140351e-01, 4.27083333e-01, 4.27083333e-01,
4.27083333e-01, 5.72916667e-01, 7.13580247e-01,
1.00000000e+00, 1.05333333e+00, 1.10000000e+00,
1.10000000e+00, 1.10000000e+00, 1.10000000e+00],
[ -1.00000000e+00, -7.13580247e-01, -5.72916667e-01,
-4.27083333e-01, -4.27083333e-01, -4.27083333e-01,
-1.56140351e-01, 2.09780513e-16, 1.26984127e-01,
2.91555556e-01, 5.72916667e-01, 5.72916667e-01,
5.72916667e-01, 5.72916667e-01, 7.13580247e-01,
1.00000000e+00, 1.05333333e+00, 1.10000000e+00,
1.18294574e+00, 1.18294574e+00, 1.18294574e+00],
[ -1.00000000e+00, -7.37704918e-01, -5.72916667e-01,
-4.27083333e-01, -2.62295082e-01, -2.62295082e-01,
4.17271323e-16, 1.56140351e-01, 2.91555556e-01,
4.36619718e-01, 7.37704918e-01, 7.37704918e-01,
7.13580247e-01, 7.13580247e-01, 7.37704918e-01,
1.00000000e+00, 1.05641026e+00, 1.10000000e+00,
1.18294574e+00, 1.34414414e+00, 1.34414414e+00],
[ -1.00000000e+00, -7.37704918e-01, -5.72916667e-01,
-4.27083333e-01, -2.62295082e-01, -2.77555756e-17,
2.62295082e-01, 4.27083333e-01, 5.72916667e-01,
7.37704918e-01, 1.00000000e+00, 1.00000000e+00,
1.00000000e+00, 1.00000000e+00, 1.00000000e+00,
1.00000000e+00, 1.05641026e+00, 1.10000000e+00,
1.18294574e+00, 1.34414414e+00, 1.66666667e+00],
[ -7.37704918e-01, -4.36619718e-01, -2.91555556e-01,
-1.56140351e-01, 2.29733650e-16, 2.62295082e-01,
2.62295082e-01, 4.27083333e-01, 5.72916667e-01,
7.37704918e-01, 1.00000000e+00, 1.05641026e+00,
1.05333333e+00, 1.05333333e+00, 1.05641026e+00,
1.05641026e+00, 1.05641026e+00, 1.10000000e+00,
1.18294574e+00, 1.34414414e+00, 1.65555556e+00],
[ -5.72916667e-01, -2.91555556e-01, -1.26984127e-01,
2.42054439e-16, 1.56140351e-01, 4.27083333e-01,
4.27083333e-01, 4.27083333e-01, 5.72916667e-01,
7.13580247e-01, 1.00000000e+00, 1.05333333e+00,
1.10000000e+00, 1.10000000e+00, 1.10000000e+00,
1.10000000e+00, 1.10000000e+00, 1.10000000e+00,
1.18294574e+00, 1.29494949e+00, 1.62857143e+00],
[ -4.27083333e-01, -1.56140351e-01, 6.45478503e-17,
1.26984127e-01, 2.91555556e-01, 5.72916667e-01,
5.72916667e-01, 5.72916667e-01, 5.72916667e-01,
7.13580247e-01, 1.00000000e+00, 1.05333333e+00,
1.10000000e+00, 1.18294574e+00, 1.18294574e+00,
1.18294574e+00, 1.18294574e+00, 1.18294574e+00,
1.18294574e+00, 1.29494949e+00, 1.62857143e+00],
[ -2.62295082e-01, 1.96914557e-16, 1.56140351e-01,
2.91555556e-01, 4.36619718e-01, 7.37704918e-01,
7.37704918e-01, 7.13580247e-01, 7.13580247e-01,
7.37704918e-01, 1.00000000e+00, 1.05641026e+00,
1.10000000e+00, 1.18294574e+00, 1.34414414e+00,
1.34414414e+00, 1.34414414e+00, 1.29494949e+00,
1.29494949e+00, 1.34414414e+00, 1.65555556e+00],
[ -2.77555756e-17, 2.62295082e-01, 4.27083333e-01,
5.72916667e-01, 7.37704918e-01, 1.00000000e+00,
1.00000000e+00, 1.00000000e+00, 1.00000000e+00,
1.00000000e+00, 1.00000000e+00, 1.05641026e+00,
1.10000000e+00, 1.18294574e+00, 1.34414414e+00,
1.66666667e+00, 1.65555556e+00, 1.62857143e+00,
1.62857143e+00, 1.65555556e+00, 1.66666667e+00]])
# Ensure results are within expected limits
np.testing.assert_allclose(z1, expected)
if __name__ == '__main__':
tst.run_module_suite()