<commit_before>﻿#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
test_pepapot
----------------------------------

Tests for `pepapot` module.
"""

import random
import unittest
import io
import logging
import functools

from pepapot import pepapot
Action = pepapot.Action

## We begin with testing for the expression parser and evaluator


class TestExpression(unittest.TestCase):
    def setUp(self):
        self.expression_source = "1 + 2"
        self.expected_result = 3
        self.environment = None

    def evaluate_expression(self):
        parsed = pepapot.expr_grammar.parseString(self.expression_source,
                                                  parseAll=True)
        expr = parsed[0]
        result = expr.get_value(environment=self.environment)
        return result

    def test_evaluation(self):
        result = self.evaluate_expression()
        self.assertEqual(result, self.expected_result)


class TestNameExpression(TestExpression):
    def setUp(self):
        self.expression_source = "x * 10"
        self.expected_result = 100
        self.environment = {"x": 10}


class TestMissingNameExpression(TestExpression):
    def setUp(self):
        self.expression_source = "x * y"
        self.environment = {"x": 10}

    def test_evaluation(self):
        self.assertRaises(KeyError, self.evaluate_expression)


class TestConstantDefinitions(unittest.TestCase):
    def setUp(self):
        self.source = """a = 1.0;
                         b = a + 10.0;
                         c = b - 1.0;
                         d = c * c;
                         e = d / 20.0;
                         f = 2.0 ** 3;
                      """
        self.expected = {"a": 1.0,
                         "b": 11.0,
                         "c": 10.0,
                         "d": 100.0,
                         "e": 5.0,
                         "f": 8.0
                         }

    def test_evaluation(self):
        grammar = pepapot.ConstantDefinition.list_grammar
        parse_result = grammar.parseString(self.source, parseAll=True)
        defs = parse_result[0]
        environment = dict()
        for definition in defs:
            value = definition.rhs.get_value(environment=environment)
            environment[definition.lhs] = value
        self.assertDictEqual(environment, self.expected)


simple_components = """
P = (a,1.0).P1;
P1 = (b, 1.0).P;

Q = (a,1.0).Q1;
Q1 = (b, 1.0).Q;
"""

simple_choice_component = """
P = (a, 1.0).P1 + (b, 1.0).P2;
P1 = (c, 1.0).P;
P2 = (d, 1.0).P;
"""

one_expr = pepapot.NumExpression(1.0)
two_expr = pepapot.NumExpression(2.0)


def is_valid_gen_matrix(testcase, model_solver):
    for (row_number, row) in enumerate(model_solver.gen_matrix):
        testcase.assertAlmostEqual(0.0, sum(row))
        testcase.assertTrue(row[row_number] < 0.0)


class TestSimpleNoCoop(unittest.TestCase):
    """This tests a very simple test model. It also serves as a base class from
       which all other cases testing particular models should derive.
       A subclass should re-write the setup method populating all of the
       expected values with appropriate ones.
    """
    def setUp(self):
        self.model_source = simple_components + "\nP || Q"
        self.expected_used_process_names = set(["P", "P1", "Q", "Q1"])
        self.expected_defined_process_names = set(["P", "P1", "Q", "Q1"])

        self.expected_actions_dictionary = dict()
        self.expected_actions_dictionary["P"] = [Action("a", one_expr, "P1")]
        self.expected_actions_dictionary["P1"] = [Action("b", one_expr, "P")]
        self.expected_actions_dictionary["Q"] = [Action("a", one_expr, "Q1")]
        self.expected_actions_dictionary["Q1"] = [Action("b", one_expr, "Q")]

        self.expected_shared_actions = set()

        self.expected_initial_state = ("P", "Q")
        self.expected_state_space_size = 4

        self.expected_solution = [(("P", "Q"), 0.25),
                                  (("P1", "Q"), 0.25),
                                  (("P", "Q1"), 0.25),
                                  (("P1", "Q1"), 0.25)]

        self.expected_utilisations = [dict([("P", 0.5),
                                            ("P1", 0.5)]),
                                      dict([("Q", 0.5),
                                            ("Q1", 0.5)])]

    # I had separate methods for testing each of these things, but I found
    # that unittest re-created this class for each test, hence I was not
    # remembering the previous computed values. Not a huge problem as
    # everything was computed fast enough. But still, I think I prefer this.
    def test_everything(self):
        model = pepapot.parse_model(self.model_source)

        # Test the parser
        system_equation = model.system_equation
        shared_actions = pepapot.CompSharedActions.get_result(system_equation)
        self.assertEqual(shared_actions, self.expected_shared_actions)

       # Test the defined names
        defined_names = model.defined_process_names()
        self.assertEqual(defined_names, self.expected_defined_process_names)

        # Test the used names
        used_names = model.used_process_names()
        self.assertEqual(used_names, self.expected_used_process_names)

        # Test the set of actions
        actual_actions = model.get_process_actions()
        self.assertEqual(actual_actions, self.expected_actions_dictionary)

        model_solver = pepapot.ModelSolver(model)

        # Test the initial state
        self.assertEqual(model_solver.initial_state,
                         self.expected_initial_state)

        # Test the size of the state space produced
        model_solver.log_state_space()
        self.assertEqual(len(model_solver.state_space),
                         self.expected_state_space_size)

        # Test the generator matrix
        is_valid_gen_matrix(self, model_solver)

        # Test the steady state solution
        steady_solution = model_solver.steady_solution
        self.assertAlmostEqual(sum(steady_solution), 1.0,
                               msg="Probabilities do not add up to one")
        for (state, expected_probability) in self.expected_solution:
            state_number, transitions = model_solver.state_space[state]
            probability = steady_solution[state_number]
            message = ("Probability for state: " + str(state) +
                       " is calculated as " + str(probability) +
                       " rather than the expected " +
                       str(expected_probability))
            self.assertAlmostEqual(probability, expected_probability,
                                   msg=message)

        # Test the steady state utilisations
        steady_utilisations = model_solver.steady_utilisations
        for actual_utils, expected_utils in zip(steady_utilisations,
                                                self.expected_utilisations):
            for process, utilisation in expected_utils.items():
                self.assertAlmostEqual(actual_utils[process], utilisation)


class TestSimpleSingleCoop(TestSimpleNoCoop):
    """This model has most of the same results as the model without any
       cooperation so we inherit from that and then change the model rather
       than from the base test model class.
    """
    def setUp(self):
        # This model has most of the same results as the model without
        # any cooperation so we inhebase test case
        # So I'll just call super here rather than copy and paste, but in
        # general it shouldn't be necessary to call super here.
        super(TestSimpleSingleCoop, self).setUp()
        self.model_source = simple_components + "\nP <a> Q"
        self.expected_shared_actions = set(["a"])
        self.expected_solution = [(("P", "Q"), 0.4),
                                  (("P1", "Q"), 0.2),
                                  (("P", "Q1"), 0.2),
                                  (("P1", "Q1"), 0.2)]
        self.expected_utilisations = [dict([("P", 0.6),
                                            ("P1", 0.4)]),
                                      dict([("Q", 0.6),
                                            ("Q1", 0.4)])]


class TestSimpleDoubleCoop(TestSimpleNoCoop):
    """Similar to the above case we're only using super here because we can
       and so many of the expected results are the same.
    """
    def setUp(self):
        super(TestSimpleDoubleCoop, self).setUp()
        self.model_source = simple_components + "\nP <a, b> Q"
        self.expected_shared_actions = set(["a", "b"])
        self.expected_state_space_size = 2
        self.expected_solution = [(("P", "Q"), 0.5),
                                  (("P1", "Q1"), 0.5)]
        self.expected_utilisations = [dict([("P", 0.5),
                                            ("P1", 0.5)]),
                                      dict([("Q", 0.5),
                                            ("Q1", 0.5)])]


class TestApparentRate(TestSimpleNoCoop):
    """A test designed to test the computation of a simple apparent rate"""
    def setUp(self):
        self.model_source = """
P = (a, 1.0).P1;
P1 = (b, 1.0).P;

Q = (a, 1.0).Q1;
Q1 = (b, 2.0).Q;

R = (a, 1.0).R1;
R1 = (b, 10.0).R;

R <b> (P || Q)
        """
        self.expected_shared_actions = set(["b"])

        set_of_names = set(["P", "P1", "Q", "Q1", "R", "R1"])
        self.expected_used_process_names = set_of_names
        self.expected_defined_process_names = set_of_names

        ten_expr = pepapot.NumExpression(10.0)
        self.expected_actions_dictionary = dict()
        self.expected_actions_dictionary["P"] = [Action("a", one_expr, "P1")]
        self.expected_actions_dictionary["P1"] = [Action("b", one_expr, "P")]
        self.expected_actions_dictionary["Q"] = [Action("a", one_expr, "Q1")]
        self.expected_actions_dictionary["Q1"] = [Action("b", two_expr, "Q")]
        self.expected_actions_dictionary["R"] = [Action("a", one_expr, "R1")]
        self.expected_actions_dictionary["R1"] = [Action("b", ten_expr, "R")]

        self.expected_initial_state = ("R", ("P", "Q"))
        self.expected_state_space_size = 8

        self.expected_solution = [(("R", ("P", "Q")), 0.0684931506849),
                                  (("R", ("P1", "Q")), 0.184931506849),
                                  (("R", ("P", "Q1")), 0.109589041096),
                                  (("R", ("P1", "Q1")), 0.294520547945),

                                  (("R1", ("P", "Q")), 0.0342465753425),
                                  (("R1", ("P1", "Q")), 0.109589041096),
                                  (("R1", ("P", "Q1")), 0.0479452054795),
                                  (("R1", ("P1", "Q1")), 0.150684931507)]

        self.expected_utilisations = [dict([("R", 0.657534246575),
                                            ("R1", 0.342465753425)]),
                                      dict([("P", 0.260273972603),
                                            ("P1", 0.739726027397)]),
                                      dict([("Q", 0.397260273973),
                                            ("Q1", 0.602739726027)])]


class TestApparentRateWithConstantDefs(TestApparentRate):
    def setUp(self):
        super(TestApparentRateWithConstantDefs, self).setUp()
        self.model_source = """
r = 1.0;
s = 2.0;
t = 10.0;
P = (a, r).P1;
P1 = (b, r).P;

Q = (a, r).Q1;
Q1 = (b, s).Q;

R = (a, r).R1;
R1 = (b, t).R;

R <b> (P || Q)
        """
        r_expr = pepapot.NameExpression("r")
        s_expr = pepapot.NameExpression("s")
        t_expr = pepapot.NameExpression("t")
        self.expected_actions_dictionary = dict()
        self.expected_actions_dictionary["P"] = [Action("a", r_expr, "P1")]
        self.expected_actions_dictionary["P1"] = [Action("b", r_expr, "P")]
        self.expected_actions_dictionary["Q"] = [Action("a", r_expr, "Q1")]
        self.expected_actions_dictionary["Q1"] = [Action("b", s_expr, "Q")]
        self.expected_actions_dictionary["R"] = [Action("a", r_expr, "R1")]
        self.expected_actions_dictionary["R1"] = [Action("b", t_expr, "R")]


class TestSimpleArray(TestSimpleNoCoop):
    def setUp(self):
        super(TestSimpleArray, self).setUp()
        self.model_source = simple_components + "\nP[3] <a> Q[3]"
        self.expected_shared_actions = set(["a"])
        self.expected_state_space_size = 16
        self.expected_initial_state = ((('P', 3), ('P1', 0)),
                                       (('Q', 3), ('Q1', 0)))
        self.expected_solution = [
            (((('P', 3), ('P1', 0)),
              (('Q', 3), ('Q1', 0))), 0.057908355442009764),
            (((('P', 2), ('P1', 1)),
              (('Q', 2), ('Q1', 1))), 0.16193377223150243),
            (((('P', 1), ('P1', 2)),
              (('Q', 1), ('Q1', 2))), 0.10592512528249978),
            (((('P', 3), ('P1', 0)),
              (('Q', 2), ('Q1', 1))), 0.08686253316301464),
            (((('P', 2), ('P1', 1)),
              (('Q', 3), ('Q1', 0))), 0.08686253316301465),
            (((('P', 0), ('P1', 3)),
              (('Q', 0), ('Q1', 3))), 0.017654187547083297),
            (((('P', 2), ('P1', 1)),
              (('Q', 1), ('Q1', 2))), 0.11850250564999511),
            (((('P', 1), ('P1', 2)),
              (('Q', 2), ('Q1', 1))), 0.11850250564999514),
            (((('P', 1), ('P1', 2)),
              (('Q', 0), ('Q1', 3))), 0.03429301365824899),
            (((('P', 0), ('P1', 3)),
              (('Q', 1), ('Q1', 2))), 0.03429301365824901),
            (((('P', 3), ('P1', 0)),
              (('Q', 1), ('Q1', 2))), 0.049326913628770744),
            (((('P', 1), ('P1', 2)),
              (('Q', 3), ('Q1', 0))), 0.04932691362877076),
            (((('P', 2), ('P1', 1)),
              (('Q', 0), ('Q1', 3))), 0.029478235236317183),
            (((('P', 0), ('P1', 3)),
              (('Q', 2), ('Q1', 1))), 0.029478235236317187),
            (((('P', 3), ('P1', 0)),
              (('Q', 0), ('Q1', 3))), 0.009826078412105728),
            (((('P', 0), ('P1', 3)),
              (('Q', 3), ('Q1', 0))), 0.009826078412105735)]
        self.expected_utilisations = [dict([("P", 1.7133732927188765),
                                            ("P1", 1.2866267072811248)]),
                                      dict([("Q", 1.7133732927188765),
                                            ("Q1", 1.2866267072811248)])]


class TestSelfLoopArray(TestSimpleNoCoop):
    """Test whether we correctly deal with a process with a self-loop. Partly
       this test was added to gain extra coverage of tests which were missing
       a couple of lines which expressely dealt with that situation for
       aggregation.
    """
    def setUp(self):
        self.model_source = """P = (a, 1.0).P;
                               Q = (a, 1.0).Q1;
                               Q1 = (b, 1.0).Q;
                               P[3] <a> Q[3]
                            """
        self.expected_used_process_names = set(["P", "Q", "Q1"])
        self.expected_defined_process_names = set(["P", "Q", "Q1"])

        self.expected_actions_dictionary = dict()
        self.expected_actions_dictionary["P"] = [Action("a", one_expr, "P")]
        self.expected_actions_dictionary["Q"] = [Action("a", one_expr, "Q1")]
        self.expected_actions_dictionary["Q1"] = [Action("b", one_expr, "Q")]
        self.expected_shared_actions = set(["a"])
        self.expected_state_space_size = 4
        self.expected_initial_state = ((('P', 3),),
                                       (('Q', 3), ('Q1', 0)))
        self.expected_solution = [(((('P', 3),),
                                    (('Q', 3), ('Q1', 0))), 0.125),
                                  (((('P', 3),),
                                    (('Q', 2), ('Q1', 1))), 0.375),
                                  (((('P', 3),),
                                    (('Q', 1), ('Q1', 2))), 0.375),
                                  (((('P', 3),),
                                    (('Q', 0), ('Q1', 3))), 0.125)
                                  ]
        self.expected_utilisations = [dict([("P", 3.0)]),
                                      dict([("Q", 1.5),
                                            ("Q1", 1.5)
                                            ])]


class TestThreeStateArray(TestSimpleNoCoop):
    """Test whether we correctly deal with a process with a self-loop. Partly
       this test was added to gain extra coverage of tests which were missing
       a couple of lines which expressely dealt with that situation for
       aggregation.
    """
    def setUp(self):
        self.model_source = """P  = (a, 1.0).P1;
                               P1 = (b, 1.0).P2;
                               P2 = (c, 1.0).P;
                               P[3]
                            """
        self.expected_used_process_names = set(["P", "P1", "P2"])
        self.expected_defined_process_names = set(["P", "P1", "P2"])

        self.expected_actions_dictionary = dict()
        self.expected_actions_dictionary["P"] = [Action("a", one_expr, "P1")]
        self.expected_actions_dictionary["P1"] = [Action("b", one_expr, "P2")]
        self.expected_actions_dictionary["P2"] = [Action("c", one_expr, "P")]
        self.expected_shared_actions = set([])
        self.expected_state_space_size = 10
        self.expected_initial_state = (('P', 3), ('P1', 0), ('P2', 0))
        self.expected_solution = [
            ((('P', 3), ('P1', 0), ('P2', 0)), 0.03703703703703704),
            ((('P', 2), ('P1', 1), ('P2', 0)), 0.11111111111111112),
            ((('P', 2), ('P1', 0), ('P2', 1)), 0.11111111111111113),
            ((('P', 1), ('P1', 2), ('P2', 0)), 0.11111111111111112),
            ((('P', 1), ('P1', 1), ('P2', 1)), 0.22222222222222227),
            ((('P', 1), ('P1', 0), ('P2', 2)), 0.11111111111111112),
            ((('P', 0), ('P1', 3), ('P2', 0)), 0.03703703703703704),
            ((('P', 0), ('P1', 2), ('P2', 1)), 0.11111111111111113),
            ((('P', 0), ('P1', 1), ('P2', 2)), 0.11111111111111113),
            ((('P', 0), ('P1', 0), ('P2', 3)), 0.03703703703703705)
            ]
        self.expected_utilisations = [dict([("P", 1.0),
                                            ("P1", 1.0),
                                            ("P2", 1.0)
                                            ])]


class TestSimpleAlias(TestSimpleNoCoop):
    """Similar to the above case we're only using super here because we can
       and so many of the expected results are the same.
    """
    def setUp(self):
        super(TestSimpleAlias, self).setUp()
        self.model_source = """A = P;
                               P = (a,1.0).P1;
                               P1 = (b, 1.0).A;

                               Q = (a,1.0).Q1;
                               Q1 = (b, 1.0).Q;

                               A || Q
                            """
        self.expected_defined_process_names.add("A")
        self.expected_used_process_names.add("A")

        self.expected_actions_dictionary = dict()
        self.expected_actions_dictionary["P"] = [Action("a", one_expr, "P1")]
        self.expected_actions_dictionary["P1"] = [Action("b", one_expr, "A")]
        self.expected_actions_dictionary["Q"] = [Action("a", one_expr, "Q1")]
        self.expected_actions_dictionary["Q1"] = [Action("b", one_expr, "Q")]
        self.expected_actions_dictionary["A"] = [Action("a", one_expr, "P1")]

        self.expected_initial_state = ("A", "Q")
        self.expected_state_space_size = 4

        self.expected_solution = [(("A", "Q"), 0.25),
                                  (("P1", "Q"), 0.25),
                                  (("A", "Q1"), 0.25),
                                  (("P1", "Q1"), 0.25)]

        self.expected_utilisations = [dict([("A", 0.5),
                                            ("P1", 0.5)]),
                                      dict([("Q", 0.5),
                                            ("Q1", 0.5)])]


class TestSimpleChoice(TestSimpleNoCoop):
    def setUp(self):
        self.model_source = simple_choice_component + "\nP"

        self.expected_used_process_names = set(["P", "P1", "P2"])
        self.expected_defined_process_names = self.expected_used_process_names

        self.expected_actions_dictionary = dict()
        self.expected_actions_dictionary["P"] = [Action("a", one_expr, "P1"),
                                                 Action("b", one_expr, "P2")]
        self.expected_actions_dictionary["P1"] = [Action("c", one_expr, "P")]
        self.expected_actions_dictionary["P2"] = [Action("d", one_expr, "P")]

        self.expected_shared_actions = set()

        self.expected_initial_state = "P"
        self.expected_state_space_size = 3

        self.expected_solution = [("P", 1.0 / 3.0),
                                  ("P1", 1.0 / 3.0),
                                  ("P2", 1.0 / 3.0)]<commit_msg>Added an expected failing test for the awkward alias problem. I'm not sure it is a large problem that we must deal with, but if it ever comes up we could simply have a pass over the PEPA model which removes the simple aliases.<commit_after>﻿#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
test_pepapot
----------------------------------

Tests for `pepapot` module.
"""

import random
import unittest
import io
import logging
import functools

from pepapot import pepapot
Action = pepapot.Action

## We begin with testing for the expression parser and evaluator


class TestExpression(unittest.TestCase):
    def setUp(self):
        self.expression_source = "1 + 2"
        self.expected_result = 3
        self.environment = None

    def evaluate_expression(self):
        parsed = pepapot.expr_grammar.parseString(self.expression_source,
                                                  parseAll=True)
        expr = parsed[0]
        result = expr.get_value(environment=self.environment)
        return result

    def test_evaluation(self):
        result = self.evaluate_expression()
        self.assertEqual(result, self.expected_result)


class TestNameExpression(TestExpression):
    def setUp(self):
        self.expression_source = "x * 10"
        self.expected_result = 100
        self.environment = {"x": 10}


class TestMissingNameExpression(TestExpression):
    def setUp(self):
        self.expression_source = "x * y"
        self.environment = {"x": 10}

    def test_evaluation(self):
        self.assertRaises(KeyError, self.evaluate_expression)


class TestConstantDefinitions(unittest.TestCase):
    def setUp(self):
        self.source = """a = 1.0;
                         b = a + 10.0;
                         c = b - 1.0;
                         d = c * c;
                         e = d / 20.0;
                         f = 2.0 ** 3;
                      """
        self.expected = {"a": 1.0,
                         "b": 11.0,
                         "c": 10.0,
                         "d": 100.0,
                         "e": 5.0,
                         "f": 8.0
                         }

    def test_evaluation(self):
        grammar = pepapot.ConstantDefinition.list_grammar
        parse_result = grammar.parseString(self.source, parseAll=True)
        defs = parse_result[0]
        environment = dict()
        for definition in defs:
            value = definition.rhs.get_value(environment=environment)
            environment[definition.lhs] = value
        self.assertDictEqual(environment, self.expected)


simple_components = """
P = (a,1.0).P1;
P1 = (b, 1.0).P;

Q = (a,1.0).Q1;
Q1 = (b, 1.0).Q;
"""

simple_choice_component = """
P = (a, 1.0).P1 + (b, 1.0).P2;
P1 = (c, 1.0).P;
P2 = (d, 1.0).P;
"""

one_expr = pepapot.NumExpression(1.0)
two_expr = pepapot.NumExpression(2.0)


def is_valid_gen_matrix(testcase, model_solver):
    for (row_number, row) in enumerate(model_solver.gen_matrix):
        testcase.assertAlmostEqual(0.0, sum(row))
        testcase.assertTrue(row[row_number] < 0.0)


class TestSimpleNoCoop(unittest.TestCase):
    """This tests a very simple test model. It also serves as a base class from
       which all other cases testing particular models should derive.
       A subclass should re-write the setup method populating all of the
       expected values with appropriate ones.
    """
    def setUp(self):
        self.model_source = simple_components + "\nP || Q"
        self.expected_used_process_names = set(["P", "P1", "Q", "Q1"])
        self.expected_defined_process_names = set(["P", "P1", "Q", "Q1"])

        self.expected_actions_dictionary = dict()
        self.expected_actions_dictionary["P"] = [Action("a", one_expr, "P1")]
        self.expected_actions_dictionary["P1"] = [Action("b", one_expr, "P")]
        self.expected_actions_dictionary["Q"] = [Action("a", one_expr, "Q1")]
        self.expected_actions_dictionary["Q1"] = [Action("b", one_expr, "Q")]

        self.expected_shared_actions = set()

        self.expected_initial_state = ("P", "Q")
        self.expected_state_space_size = 4

        self.expected_solution = [(("P", "Q"), 0.25),
                                  (("P1", "Q"), 0.25),
                                  (("P", "Q1"), 0.25),
                                  (("P1", "Q1"), 0.25)]

        self.expected_utilisations = [dict([("P", 0.5),
                                            ("P1", 0.5)]),
                                      dict([("Q", 0.5),
                                            ("Q1", 0.5)])]

    # I had separate methods for testing each of these things, but I found
    # that unittest re-created this class for each test, hence I was not
    # remembering the previous computed values. Not a huge problem as
    # everything was computed fast enough. But still, I think I prefer this.
    def test_everything(self):
        model = pepapot.parse_model(self.model_source)

        # Test the parser
        system_equation = model.system_equation
        shared_actions = pepapot.CompSharedActions.get_result(system_equation)
        self.assertEqual(shared_actions, self.expected_shared_actions)

       # Test the defined names
        defined_names = model.defined_process_names()
        self.assertEqual(defined_names, self.expected_defined_process_names)

        # Test the used names
        used_names = model.used_process_names()
        self.assertEqual(used_names, self.expected_used_process_names)

        # Test the set of actions
        actual_actions = model.get_process_actions()
        self.assertEqual(actual_actions, self.expected_actions_dictionary)

        model_solver = pepapot.ModelSolver(model)

        # Test the initial state
        self.assertEqual(model_solver.initial_state,
                         self.expected_initial_state)

        # Test the size of the state space produced
        model_solver.log_state_space()
        self.assertEqual(len(model_solver.state_space),
                         self.expected_state_space_size)

        # Test the generator matrix
        is_valid_gen_matrix(self, model_solver)

        # Test the steady state solution
        steady_solution = model_solver.steady_solution
        self.assertAlmostEqual(sum(steady_solution), 1.0,
                               msg="Probabilities do not add up to one")
        for (state, expected_probability) in self.expected_solution:
            state_number, transitions = model_solver.state_space[state]
            probability = steady_solution[state_number]
            message = ("Probability for state: " + str(state) +
                       " is calculated as " + str(probability) +
                       " rather than the expected " +
                       str(expected_probability))
            self.assertAlmostEqual(probability, expected_probability,
                                   msg=message)

        # Test the steady state utilisations
        steady_utilisations = model_solver.steady_utilisations
        for actual_utils, expected_utils in zip(steady_utilisations,
                                                self.expected_utilisations):
            for process, utilisation in expected_utils.items():
                self.assertAlmostEqual(actual_utils[process], utilisation)


class TestSimpleSingleCoop(TestSimpleNoCoop):
    """This model has most of the same results as the model without any
       cooperation so we inherit from that and then change the model rather
       than from the base test model class.
    """
    def setUp(self):
        # This model has most of the same results as the model without
        # any cooperation so we inhebase test case
        # So I'll just call super here rather than copy and paste, but in
        # general it shouldn't be necessary to call super here.
        super(TestSimpleSingleCoop, self).setUp()
        self.model_source = simple_components + "\nP <a> Q"
        self.expected_shared_actions = set(["a"])
        self.expected_solution = [(("P", "Q"), 0.4),
                                  (("P1", "Q"), 0.2),
                                  (("P", "Q1"), 0.2),
                                  (("P1", "Q1"), 0.2)]
        self.expected_utilisations = [dict([("P", 0.6),
                                            ("P1", 0.4)]),
                                      dict([("Q", 0.6),
                                            ("Q1", 0.4)])]


class TestSimpleDoubleCoop(TestSimpleNoCoop):
    """Similar to the above case we're only using super here because we can
       and so many of the expected results are the same.
    """
    def setUp(self):
        super(TestSimpleDoubleCoop, self).setUp()
        self.model_source = simple_components + "\nP <a, b> Q"
        self.expected_shared_actions = set(["a", "b"])
        self.expected_state_space_size = 2
        self.expected_solution = [(("P", "Q"), 0.5),
                                  (("P1", "Q1"), 0.5)]
        self.expected_utilisations = [dict([("P", 0.5),
                                            ("P1", 0.5)]),
                                      dict([("Q", 0.5),
                                            ("Q1", 0.5)])]


class TestApparentRate(TestSimpleNoCoop):
    """A test designed to test the computation of a simple apparent rate"""
    def setUp(self):
        self.model_source = """
P = (a, 1.0).P1;
P1 = (b, 1.0).P;

Q = (a, 1.0).Q1;
Q1 = (b, 2.0).Q;

R = (a, 1.0).R1;
R1 = (b, 10.0).R;

R <b> (P || Q)
        """
        self.expected_shared_actions = set(["b"])

        set_of_names = set(["P", "P1", "Q", "Q1", "R", "R1"])
        self.expected_used_process_names = set_of_names
        self.expected_defined_process_names = set_of_names

        ten_expr = pepapot.NumExpression(10.0)
        self.expected_actions_dictionary = dict()
        self.expected_actions_dictionary["P"] = [Action("a", one_expr, "P1")]
        self.expected_actions_dictionary["P1"] = [Action("b", one_expr, "P")]
        self.expected_actions_dictionary["Q"] = [Action("a", one_expr, "Q1")]
        self.expected_actions_dictionary["Q1"] = [Action("b", two_expr, "Q")]
        self.expected_actions_dictionary["R"] = [Action("a", one_expr, "R1")]
        self.expected_actions_dictionary["R1"] = [Action("b", ten_expr, "R")]

        self.expected_initial_state = ("R", ("P", "Q"))
        self.expected_state_space_size = 8

        self.expected_solution = [(("R", ("P", "Q")), 0.0684931506849),
                                  (("R", ("P1", "Q")), 0.184931506849),
                                  (("R", ("P", "Q1")), 0.109589041096),
                                  (("R", ("P1", "Q1")), 0.294520547945),

                                  (("R1", ("P", "Q")), 0.0342465753425),
                                  (("R1", ("P1", "Q")), 0.109589041096),
                                  (("R1", ("P", "Q1")), 0.0479452054795),
                                  (("R1", ("P1", "Q1")), 0.150684931507)]

        self.expected_utilisations = [dict([("R", 0.657534246575),
                                            ("R1", 0.342465753425)]),
                                      dict([("P", 0.260273972603),
                                            ("P1", 0.739726027397)]),
                                      dict([("Q", 0.397260273973),
                                            ("Q1", 0.602739726027)])]


class TestApparentRateWithConstantDefs(TestApparentRate):
    def setUp(self):
        super(TestApparentRateWithConstantDefs, self).setUp()
        self.model_source = """
r = 1.0;
s = 2.0;
t = 10.0;
P = (a, r).P1;
P1 = (b, r).P;

Q = (a, r).Q1;
Q1 = (b, s).Q;

R = (a, r).R1;
R1 = (b, t).R;

R <b> (P || Q)
        """
        r_expr = pepapot.NameExpression("r")
        s_expr = pepapot.NameExpression("s")
        t_expr = pepapot.NameExpression("t")
        self.expected_actions_dictionary = dict()
        self.expected_actions_dictionary["P"] = [Action("a", r_expr, "P1")]
        self.expected_actions_dictionary["P1"] = [Action("b", r_expr, "P")]
        self.expected_actions_dictionary["Q"] = [Action("a", r_expr, "Q1")]
        self.expected_actions_dictionary["Q1"] = [Action("b", s_expr, "Q")]
        self.expected_actions_dictionary["R"] = [Action("a", r_expr, "R1")]
        self.expected_actions_dictionary["R1"] = [Action("b", t_expr, "R")]


class TestSimpleArray(TestSimpleNoCoop):
    def setUp(self):
        super(TestSimpleArray, self).setUp()
        self.model_source = simple_components + "\nP[3] <a> Q[3]"
        self.expected_shared_actions = set(["a"])
        self.expected_state_space_size = 16
        self.expected_initial_state = ((('P', 3), ('P1', 0)),
                                       (('Q', 3), ('Q1', 0)))
        self.expected_solution = [
            (((('P', 3), ('P1', 0)),
              (('Q', 3), ('Q1', 0))), 0.057908355442009764),
            (((('P', 2), ('P1', 1)),
              (('Q', 2), ('Q1', 1))), 0.16193377223150243),
            (((('P', 1), ('P1', 2)),
              (('Q', 1), ('Q1', 2))), 0.10592512528249978),
            (((('P', 3), ('P1', 0)),
              (('Q', 2), ('Q1', 1))), 0.08686253316301464),
            (((('P', 2), ('P1', 1)),
              (('Q', 3), ('Q1', 0))), 0.08686253316301465),
            (((('P', 0), ('P1', 3)),
              (('Q', 0), ('Q1', 3))), 0.017654187547083297),
            (((('P', 2), ('P1', 1)),
              (('Q', 1), ('Q1', 2))), 0.11850250564999511),
            (((('P', 1), ('P1', 2)),
              (('Q', 2), ('Q1', 1))), 0.11850250564999514),
            (((('P', 1), ('P1', 2)),
              (('Q', 0), ('Q1', 3))), 0.03429301365824899),
            (((('P', 0), ('P1', 3)),
              (('Q', 1), ('Q1', 2))), 0.03429301365824901),
            (((('P', 3), ('P1', 0)),
              (('Q', 1), ('Q1', 2))), 0.049326913628770744),
            (((('P', 1), ('P1', 2)),
              (('Q', 3), ('Q1', 0))), 0.04932691362877076),
            (((('P', 2), ('P1', 1)),
              (('Q', 0), ('Q1', 3))), 0.029478235236317183),
            (((('P', 0), ('P1', 3)),
              (('Q', 2), ('Q1', 1))), 0.029478235236317187),
            (((('P', 3), ('P1', 0)),
              (('Q', 0), ('Q1', 3))), 0.009826078412105728),
            (((('P', 0), ('P1', 3)),
              (('Q', 3), ('Q1', 0))), 0.009826078412105735)]
        self.expected_utilisations = [dict([("P", 1.7133732927188765),
                                            ("P1", 1.2866267072811248)]),
                                      dict([("Q", 1.7133732927188765),
                                            ("Q1", 1.2866267072811248)])]


class TestSelfLoopArray(TestSimpleNoCoop):
    """Test whether we correctly deal with a process with a self-loop. Partly
       this test was added to gain extra coverage of tests which were missing
       a couple of lines which expressely dealt with that situation for
       aggregation.
    """
    def setUp(self):
        self.model_source = """P = (a, 1.0).P;
                               Q = (a, 1.0).Q1;
                               Q1 = (b, 1.0).Q;
                               P[3] <a> Q[3]
                            """
        self.expected_used_process_names = set(["P", "Q", "Q1"])
        self.expected_defined_process_names = set(["P", "Q", "Q1"])

        self.expected_actions_dictionary = dict()
        self.expected_actions_dictionary["P"] = [Action("a", one_expr, "P")]
        self.expected_actions_dictionary["Q"] = [Action("a", one_expr, "Q1")]
        self.expected_actions_dictionary["Q1"] = [Action("b", one_expr, "Q")]
        self.expected_shared_actions = set(["a"])
        self.expected_state_space_size = 4
        self.expected_initial_state = ((('P', 3),),
                                       (('Q', 3), ('Q1', 0)))
        self.expected_solution = [(((('P', 3),),
                                    (('Q', 3), ('Q1', 0))), 0.125),
                                  (((('P', 3),),
                                    (('Q', 2), ('Q1', 1))), 0.375),
                                  (((('P', 3),),
                                    (('Q', 1), ('Q1', 2))), 0.375),
                                  (((('P', 3),),
                                    (('Q', 0), ('Q1', 3))), 0.125)
                                  ]
        self.expected_utilisations = [dict([("P", 3.0)]),
                                      dict([("Q", 1.5),
                                            ("Q1", 1.5)
                                            ])]


class TestThreeStateArray(TestSimpleNoCoop):
    """Test whether we correctly deal with a process with a self-loop. Partly
       this test was added to gain extra coverage of tests which were missing
       a couple of lines which expressely dealt with that situation for
       aggregation.
    """
    def setUp(self):
        self.model_source = """P  = (a, 1.0).P1;
                               P1 = (b, 1.0).P2;
                               P2 = (c, 1.0).P;
                               P[3]
                            """
        self.expected_used_process_names = set(["P", "P1", "P2"])
        self.expected_defined_process_names = set(["P", "P1", "P2"])

        self.expected_actions_dictionary = dict()
        self.expected_actions_dictionary["P"] = [Action("a", one_expr, "P1")]
        self.expected_actions_dictionary["P1"] = [Action("b", one_expr, "P2")]
        self.expected_actions_dictionary["P2"] = [Action("c", one_expr, "P")]
        self.expected_shared_actions = set([])
        self.expected_state_space_size = 10
        self.expected_initial_state = (('P', 3), ('P1', 0), ('P2', 0))
        self.expected_solution = [
            ((('P', 3), ('P1', 0), ('P2', 0)), 0.03703703703703704),
            ((('P', 2), ('P1', 1), ('P2', 0)), 0.11111111111111112),
            ((('P', 2), ('P1', 0), ('P2', 1)), 0.11111111111111113),
            ((('P', 1), ('P1', 2), ('P2', 0)), 0.11111111111111112),
            ((('P', 1), ('P1', 1), ('P2', 1)), 0.22222222222222227),
            ((('P', 1), ('P1', 0), ('P2', 2)), 0.11111111111111112),
            ((('P', 0), ('P1', 3), ('P2', 0)), 0.03703703703703704),
            ((('P', 0), ('P1', 2), ('P2', 1)), 0.11111111111111113),
            ((('P', 0), ('P1', 1), ('P2', 2)), 0.11111111111111113),
            ((('P', 0), ('P1', 0), ('P2', 3)), 0.03703703703703705)
            ]
        self.expected_utilisations = [dict([("P", 1.0),
                                            ("P1", 1.0),
                                            ("P2", 1.0)
                                            ])]


class TestSimpleAlias(TestSimpleNoCoop):
    """Similar to the above case we're only using super here because we can
       and so many of the expected results are the same.
    """
    def setUp(self):
        super(TestSimpleAlias, self).setUp()
        self.model_source = """A = P;
                               P = (a,1.0).P1;
                               P1 = (b, 1.0).A;

                               Q = (a,1.0).Q1;
                               Q1 = (b, 1.0).Q;

                               A || Q
                            """
        self.expected_defined_process_names.add("A")
        self.expected_used_process_names.add("A")

        self.expected_actions_dictionary = dict()
        self.expected_actions_dictionary["P"] = [Action("a", one_expr, "P1")]
        self.expected_actions_dictionary["P1"] = [Action("b", one_expr, "A")]
        self.expected_actions_dictionary["Q"] = [Action("a", one_expr, "Q1")]
        self.expected_actions_dictionary["Q1"] = [Action("b", one_expr, "Q")]
        self.expected_actions_dictionary["A"] = [Action("a", one_expr, "P1")]

        self.expected_initial_state = ("A", "Q")
        self.expected_state_space_size = 4

        self.expected_solution = [(("A", "Q"), 0.25),
                                  (("P1", "Q"), 0.25),
                                  (("A", "Q1"), 0.25),
                                  (("P1", "Q1"), 0.25)]

        self.expected_utilisations = [dict([("A", 0.5),
                                            ("P1", 0.5)]),
                                      dict([("Q", 0.5),
                                            ("Q1", 0.5)])]


class TestAwkwardAlias(TestSimpleNoCoop):
    def setUp(self):
        self.model_source = """A  = P;
                               P  = (a, 1.0).P1;
                               P1 = (b, 1.0).P;
                               A
                            """
        self.expected_defined_process_names = set(["A", "P", "P1"])
        self.expected_used_process_names = set(["A", "P", "P1"])

        self.expected_actions_dictionary = dict()
        self.expected_actions_dictionary["A"] = [Action("a", one_expr, "P1")]
        self.expected_actions_dictionary["P"] = [Action("a", one_expr, "P1")]
        self.expected_actions_dictionary["P1"] = [Action("b", one_expr, "P")]

        self.expected_shared_actions = set([])

        self.expected_initial_state = ("A")
        self.expected_state_space_size = 2

        self.expected_solution = [(("A"), 0.5),
                                  (("P1"), 0.5)]

        self.expected_utilisations = [dict([("A", 0.5), ("P1", 0.5)])]

    # This test is non-trivial to get correct. The problem is that we end up
    # with three states rather than two. These correspond to the states:
    # A, P and P1. Of course A and P are the same state. So we need away of
    # noting that. The alternative is just to accept that this has some
    # transient states to begin with.
    @unittest.expectedFailure
    def test_everything(self):
        super(TestAwkwardAlias, self).test_everything()


class TestSimpleChoice(TestSimpleNoCoop):
    def setUp(self):
        self.model_source = simple_choice_component + "\nP"

        self.expected_used_process_names = set(["P", "P1", "P2"])
        self.expected_defined_process_names = self.expected_used_process_names

        self.expected_actions_dictionary = dict()
        self.expected_actions_dictionary["P"] = [Action("a", one_expr, "P1"),
                                                 Action("b", one_expr, "P2")]
        self.expected_actions_dictionary["P1"] = [Action("c", one_expr, "P")]
        self.expected_actions_dictionary["P2"] = [Action("d", one_expr, "P")]

        self.expected_shared_actions = set()

        self.expected_initial_state = "P"
        self.expected_state_space_size = 3

        self.expected_solution = [("P", 1.0 / 3.0),
                                  ("P1", 1.0 / 3.0),
                                  ("P2", 1.0 / 3.0)]
{'fraction_non_alphanumeric': 0.1604070475271633, 'fraction_numerical': 0.05846139710104978, 'mean_word_length': 1.7764030612244899, 'pattern_counts': {'":': 16, '<': 17, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 14, 'word_list_counts': {'cursed_substrings.json': 187, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1420799', 'n_tokens_mistral': 14992, 'n_tokens_neox': 13161, 'n_words': 3019}