<jupyter_start><jupyter_text>XCS TutorialThis is the official tutorial for the [xcs package](https://pypi.python.org/pypi/xcs) for Python 3. You can find the latest release and get updates on the project's status at the [project home page](http://hosford42.github.io/xcs/). What is XCS?XCS is a Python 3 implementation of the XCS algorithm as described in the 2001 paper, [An Algorithmic Description of XCS](http://link.springer.com/chapter/10.1007/3-540-44640-0_15), by [<NAME>](http://www.uni-tuebingen.de/fakultaeten/mathematisch-naturwissenschaftliche-fakultaet/fachbereiche/informatik/lehrstuehle/cognitive-modeling/staff/staff/martin-v-butz.html) and [<NAME>](http://prediction-dynamics.com/). XCS is a type of [Learning Classifier System (LCS)](http://en.wikipedia.org/wiki/Learning_classifier_system), a [machine learning](http://en.wikipedia.org/wiki/Machine_learning) algorithm that utilizes a [genetic algorithm](http://en.wikipedia.org/wiki/Genetic_algorithm) acting on a rule-based system, to solve a [reinforcement learning](http://en.wikipedia.org/wiki/Reinforcement_learning) problem.In its canonical form, XCS accepts a fixed-width string of bits as its input, and attempts to select the best action from a predetermined list of choices using an evolving set of rules that match inputs and offer appropriate suggestions. It then receives a reward signal indicating the quality of its decision, which it uses to adjust the rule set that was used to make the decision. This process is subsequently repeated, allowing the algorithm to evaluate the changes it has already made and further refine the rule set.A key feature of XCS is that, unlike many other machine learning algorithms, it not only learns the optimal input/output mapping, but also produces a minimal set of rules for describing that mapping. This is a big advantage over other learning algorithms such as [neural networks](http://en.wikipedia.org/wiki/Artificial_neural_network) whose models are largely opaque to human analysis, making XCS an important tool in any data scientist's tool belt.The XCS library provides not only an implementation of the standard XCS algorithm, but a set of interfaces which together constitute a framework for implementing and experimenting with other LCS variants. Future plans for the XCS library include continued expansion of the tool set with additional algorithms, and refinement of the interface to support reinforcement learning algorithms in general. TerminologyBeing both a reinforcement learning algorithm and an evolutionary algorithm, XCS requires an understanding of terms pertaining to both. SituationA situation is just another term for an input received by the classifier. ActionAn action is an output produced by the classifier. ScenarioA series of situations, each of which the algorithm must respond to in order with an appropriate action in order to maximize the total reward received for each action. A scenario may be dynamic, meaning that later training cycles can be affected by earlier actions, or static, meaning that each training cycle is independent of the actions that came before it. Classifier RuleA classifier rule, sometimes referred to as just a *rule* or a *classifier*, is a pairing between a condition, describing which situations can be matched, and a suggested action. Each classifier rule has an associated prediction indicating the expected reward if the suggested action is taken when the condition matches the situation, a fitness indicating its suitability for reproduction and continued use in the population, and a numerosity value which indicates the number of (virtual) instances of the rule in the population. (There are other parameters associated with each rule, as well, but these are visibly important ones.) Classifier SetAlso referred to as the *population*, this is the collection of all rules currently used and tracked by the classifier. The genetic algorithm operates on this set of rules over time to optimize them for accuracy and generality in their descriptiveness of the problem space. Note that the population is virtual, meaning that if the same rule has multiple copies in the population, it is represented only once, with an associated numerosity value to indicate the number of virtual instances of the rule in the population. Match SetThe match set is the set of rules which match against the current situation. Action SetThe action set is the set of rules which match against the current situation and recommend the selected action. Thus the action set is a subset of the match set. In fact, the match set can be seen as a collection of mutually exclusive and competing action sets, from which only one is to be selected. RewardThe reward is a floating point value which acts as the signal the algorithm attempts to maximize. There are three types of reward that are commonly mentioned with respect to temporal difference learning algorithms. The *immediate reward* (aka *raw reward*) is the original, unaltered reward value returned by the scenario in response to each action. The *expected future reward* is the estimated payoff for later reward cycles, specifically excluding the current one; the prediction of the action set on the next reward cycle acts in this role in the canonical XCS algorithm. The *payoff* or *combined reward* is the combined sum of the immediate reward, plus the discounted expected future reward. (Discounted means the value is multiplied by a non-negative coefficient whose value is less than 1, which causes the algorithm to value immediate reward more highly than reward received later on.) The term *reward*, when used alone, is generally used to mean the immediate reward. PredictionA prediction is an estimate by a classifier rule or an action set as to the payoff expected to be received by taking the suggested action in the given situation. The prediction of an action set is formed by taking the fitness-weighted average of the predictions made by the individual rules within it. FitnessFitness is another floating point value similar in function to the reward, except that in this case it is an internal signal defined by the algorithm itself, which is then used as a guide for selection of which rules are to act as parents to the next generation. Each rule in the population has its own associated fitness value. In XCS, as opposed to strength-based LCS variants such as ZCS, the fitness is actually based on the accuracy of each rule's reward prediction, as opposed to its size. Thus a rule with a very low expected reward can have a high fitness provided it is accurate in its prediction of low reward, whereas a rule with very high expected reward may have low fitness because the reward it receives varies widely from one reward cycle to the next. Using reward prediction accuracy instead of reward prediction size helps XCS find rules that describe the problem in a stable, predictable way. InstallationTo install `xcs`, you will of course need a Python 3 interpreter. The latest version of the standard CPython distribution is [available for download](https://www.python.org/downloads/) from the Python Software Foundation, or if you prefer a download that comes with a long list of top-notch machine learning and scientific computing packages already built for you, I recommend [Anaconda](http://continuum.io/downloadspy34) from Continuum Analytics.Starting with Python 3.4, the standard CPython distribution comes with the package installation tool, [pip](https://pip.pypa.io/en/latest/installing.html), as part of the standard distribution. Anaconda comes with `pip` regardless of the Python version. If you have `pip`, installation of `xcs` is straight forward:```pip install xcs```If all goes as planned, you should see a message like this:```Successfully installed xcs-1.0.0```If for some reason you are unable to use `pip`, you can still install `xcs` manually. The latest release can be found [here](https://github.com/hosford42/xcs/releases) or [here](https://pypi.python.org/pypi/xcs). Download the zip file, unpack it, and cd into the directory. Then run:```python setup.py install```You should see a message indicating that the package was successfully installed. Testing the Newly Installed PackageLet's start things off with a quick test, to verify that everything has been installed properly. First, fire up the Python interpreter. We'll set up Python's built-in logging system so we can see the test's progress.<jupyter_code>import logging
logging.root.setLevel(logging.INFO)<jupyter_output><empty_output><jupyter_text>Then we import the `xcs` module and run the built-in `test()` function. By default, the `test()` function runs the canonical XCS algorithm on the 11-bit (3-bit address) MUX problem for 10,000 steps.<jupyter_code>import xcs
xcs.test()<jupyter_output><empty_output><jupyter_text>```INFO:xcs.scenarios:Possible actions:INFO:xcs.scenarios:    FalseINFO:xcs.scenarios:    TrueINFO:xcs.scenarios:Steps completed: 0INFO:xcs.scenarios:Average reward per step: 0.00000INFO:xcs.scenarios:Steps completed: 100INFO:xcs.scenarios:Average reward per step: 0.57000INFO:xcs.scenarios:Steps completed: 200INFO:xcs.scenarios:Average reward per step: 0.58500...0010 => False    Time Stamp: 9980    Average Reward: 1.0    Error: 0.0    Fitness: 0.8161150828153352    Experience: 236    Action Set Size: 25.03847865419106    Numerosity: 91111 => True    Time Stamp: 9994    Average Reward: 1.0    Error: 0.0    Fitness: 0.9749473121531844    Experience: 428    Action Set Size: 20.685392494947063    Numerosity: 11INFO:xcs:Total time: 15.05068 seconds```Your results may vary somewhat from what is shown here. XCS relies on randomization to discover new rules, so unless you set the random seed with `random.seed()`, each run will be different.  UsageNow we'll run through a quick demo of how to use existing algorithms and problems. This is essentially the same code that appears in the `test()` function we called above.First, we're going to need to import a few things:<jupyter_code>from xcs import XCSAlgorithm
from xcs.scenarios import MUXProblem, ScenarioObserver<jupyter_output><empty_output><jupyter_text>The `XCSAlgorithm` class contains the actual XCS algorithm implementation. The `ClassifierSet` class is used to represent the algorithm's state, in the form of a set of classifier rules. `MUXProblem` is the classic [multiplexer](http://en.wikipedia.org/wiki/Multiplexer) problem, which defaults to 3 address bits (11 bits total). `ScenarioObserver` is a wrapper for scenarios which logs the inputs, actions, and rewards as the algorithm attempts to solve the problem.Now that we've imported the necessary tools, we can define the actual problem, telling it to give the algorithm 10,000 reward cycles to attempt to learn the appropriate input/output mapping, and wrapping it with an observer so we can see the algorithm's progress.<jupyter_code>scenario = ScenarioObserver(MUXProblem(50000))<jupyter_output><empty_output><jupyter_text>Next, we'll create the algorithm which will be used to manage the classifier set and learn the mapping defined by the problem we have selected:<jupyter_code>algorithm = XCSAlgorithm()<jupyter_output><empty_output><jupyter_text>The algorithm's parameters are set to appropriate defaults for most problems, but it is straight forward to modify them if it becomes necessary.<jupyter_code>algorithm.exploration_probability = .1
algorithm.discount_factor = 0
algorithm.do_ga_subsumption = True
algorithm.do_action_set_subsumption = True<jupyter_output><empty_output><jupyter_text>Here we have selected an exploration probability of .1, which will sacrifice most (9 out of 10) learning opportunities in favor of taking advantage of what has already been learned so far. This makes sense in real-time learning environment; a lower value is more appropriate in cases where the classifier is being trained in advance or is being used simply to learn a minimal rule set. The discount factor is set to 0, since future rewards are not affected at all by the currently selected action. (This is not strictly necessary, since the scenario will inform the algorithm that reward chaining should not be used, but it is useful to highlight this fact.) We have also elected to turn on GA and action set subsumption, which help the system to converge to the minimal effective rule set more quickly in some types of scenarios.Next, we create the classifier set:<jupyter_code>model = algorithm.new_model(scenario)<jupyter_output><empty_output><jupyter_text>The algorithm does the work for us, initializing the classifier set as it deems appropriate for the scenario we have provided. It provides the classifier set with the possible actions that can be taken in the given scenario; these are necessary for the classifier set to perform covering operations when the algorithm determines that the classifiers in the population provide insufficient coverage for a particular situation. (Covering is the addition to the population of a randomly generated classifier rule whose condition matches the current situation.)And finally, this is where all the magic happens:<jupyter_code>model.run(scenario, learn=True)<jupyter_output><empty_output><jupyter_text>We pass the scenario to the classifier set and ask it to run to learn the appropriate input/output mapping. It executes training cycles until the scenario dictates that training should stop. Note that if you wish to see the progress as the algorithm interacts with the scenario, you will need to set the logging level to `INFO`, as described in the previous section, before calling the `run()` method.Now we can observe the fruits of our labors.<jupyter_code>print(model)<jupyter_output><empty_output><jupyter_text>```1000110100 => True    Time Stamp: 41601    Average Reward: 1e-05    Error: 1e-05    Fitness: 1e-05    Experience: 0    Action Set Size: 1    Numerosity: 1000010000 => True    Time Stamp: 48589    Average Reward: 1e-05    Error: 1e-05    Fitness: 1e-05    Experience: 0    Action Set Size: 1    Numerosity: 1...11111 => True    Time Stamp: 49968    Average Reward: 1.0    Error: 0.0    Fitness: 0.9654542879926405    Experience: 131    Action Set Size: 27.598176294274904    Numerosity: 100101 => True    Time Stamp: 49962    Average Reward: 1.0    Error: 0.0    Fitness: 0.8516265524887351    Experience: 1257    Action Set Size: 27.21325456027306    Numerosity: 13``` This gives us a printout of each classifier rule, in the form *condition => action*, followed by various stats about the rule pertaining to the algorithm we selected. The classifier set can also be accessed as an iterable container:<jupyter_code>print(len(model))
for rule in model:
    if rule.fitness > .5 and rule.experience >= 10:
        print(rule.condition, '=>', rule.action, ' [%.5f]' % rule.fitness)<jupyter_output>0001####### => True  [0.83226]
10#####00## => False  [0.80711]
110######1# => True  [0.83886]
010##1##### => True  [0.85163]
##00#0##### => False  [0.65383]
1111######1 => True  [0.96545]
100####1### => True  [0.81688]
#0111###1## => True  [0.94060]<jupyter_text>Defining New Scenario TypesTo define a new scenario type, inherit from the `Scenario` abstract class defined in the `xcs.scenarios` submodule. Suppose, as an example, that we wish to test the algorithm's ability to find a single important input bit from among a large number of irrelevant input bits.<jupyter_code>from xcs.scenarios import Scenario

class HaystackProblem(Scenario):
    pass<jupyter_output><empty_output><jupyter_text>We defined a new class, `HaystackProblem`, to represent this test case, which inherits from `Scenario` to ensure that we cannot instantiate the problem until the appropriate methods have been implemented.Now let's define an `__init__` method for this class. We'll need a parameter, `training_cycles`, to determine how many reward cycles the algorithm has to identify the "needle", and another parameter, `input_size`, to determine how big the "haystack" is.<jupyter_code>from xcs.scenarios import Scenario

class HaystackProblem(Scenario):
    
    def __init__(self, training_cycles=1000, input_size=500):
        self.input_size = input_size
        self.possible_actions = (True, False)
        self.initial_training_cycles = training_cycles
        self.remaining_cycles = training_cycles<jupyter_output><empty_output><jupyter_text>The `input_size` is saved as a member for later use. Likewise, the value of `training_cycles` was saved in two places: the `remaining_cycles` member, which tells the instance how many training cycles remain for the current run, and the `initial_training_cycles` member, which the instance will use to reset `remaining_cycles` to the original value for a new run.We also defined the `possible_actions` member, which we set to `(True, False)`. This is the value we will return when the algorithm asks for the possible actions. We will expect the algorithm to return `True` when the needle bit is set, and `False` when the needle bit is clear, in order to indicate that it has correctly identified the needle's location.Now let's define some methods for the class. The `Scenario` base class defines several abstract methods, and one abstract property:* `is_dynamic` is a property with a Boolean value that indicates whether the actions from one reward cycle can affect the rewards or situations of later reward cycles.* `get_possible_actions()` is a method that should return the actions the algorithm can take.* `reset()` should restart the problem for a new run.* `sense()` should return a new input (the "situation").* `execute(action)` should accept an action from among those returned by `get_possible_actions()`, in response to the last situation that was returned by `sense()`. It should then return a reward value indicating how well the algorithm is doing at responding correctly to each situation.* `more()` should return a Boolean value to indicate whether the algorithm has remaining reward cycles in which to learn.The abstract methods and the property must each be defined, or we will get a TypeError when we attempt to instantiate the class:<jupyter_code>problem = HaystackProblem()<jupyter_output><empty_output><jupyter_text>The implementations for the property and the methods other than `sense()` and `execute()` will be trivial, so let's start with those:<jupyter_code>from xcs.scenarios import Scenario

class HaystackProblem(Scenario):
    
    def __init__(self, training_cycles=1000, input_size=500):
        self.input_size = input_size
        self.possible_actions = (True, False)
        self.initial_training_cycles = training_cycles
        self.remaining_cycles = training_cycles

    @property
    def is_dynamic(self):
        return False
        
    def get_possible_actions(self):
        return self.possible_actions
    
    def reset(self):
        self.remaining_cycles = self.initial_training_cycles
        
    def more(self):
        return self.remaining_cycles > 0<jupyter_output><empty_output><jupyter_text>Now we are going to get into the meat of the problem. We want to give the algorithm a random string of bits of size `input_size` and have it pick out the location of the needle bit through trial and error, by telling us what it thinks the value of the needle bit is. For this to be a useful test, the needle bit needs to be in a fixed location, which we have not yet defined. Let's choose a random bit from among inputs on each run.<jupyter_code>import random

from xcs.scenarios import Scenario

class HaystackProblem(Scenario):
    
    def __init__(self, training_cycles=1000, input_size=500):
        self.input_size = input_size
        self.possible_actions = (True, False)
        self.initial_training_cycles = training_cycles
        self.remaining_cycles = training_cycles
        self.needle_index = random.randrange(input_size)

    @property
    def is_dynamic(self):
        return False
        
    def get_possible_actions(self):
        return self.possible_actions
    
    def reset(self):
        self.remaining_cycles = self.initial_training_cycles
        self.needle_index = random.randrange(self.input_size)
        
    def more(self):
        return self.remaining_cycles > 0<jupyter_output><empty_output><jupyter_text>The `sense()` method is going to create a string of random bits of size `input_size` and return it. But first it will pick out the value of the needle bit, located at `needle_index`, and store it in a new member, `needle_value`, so that `execute(action)` will know what the correct value for `action` is.<jupyter_code>import random

from xcs.scenarios import Scenario
from xcs.bitstrings import BitString

class HaystackProblem(Scenario):
    
    def __init__(self, training_cycles=1000, input_size=500):
        self.input_size = input_size
        self.possible_actions = (True, False)
        self.initial_training_cycles = training_cycles
        self.remaining_cycles = training_cycles
        self.needle_index = random.randrange(input_size)
        self.needle_value = None

    @property
    def is_dynamic(self):
        return False
        
    def get_possible_actions(self):
        return self.possible_actions
    
    def reset(self):
        self.remaining_cycles = self.initial_training_cycles
        self.needle_index = random.randrange(self.input_size)
        
    def more(self):
        return self.remaining_cycles > 0
    
    def sense(self):
        haystack = BitString.random(self.input_size)
        self.needle_value = haystack[self.needle_index]
        return haystack<jupyter_output><empty_output><jupyter_text>Now we need to define the `execute(action)` method. In order to give the algorithm appropriate feedback to make problem solvable, we should return a high reward when it guesses the correct value for the needle bit, and a low value otherwise. Thus we will return a `1` when the action is the value of the needle bit, and a `0` otherwise. We must also make sure to decrement the remaining cycles to prevent the problem from running indefinitely.<jupyter_code>import random

from xcs.scenarios import Scenario
from xcs.bitstrings import BitString

class HaystackProblem(Scenario):
    
    def __init__(self, training_cycles=1000, input_size=500):
        self.input_size = input_size
        self.possible_actions = (True, False)
        self.initial_training_cycles = training_cycles
        self.remaining_cycles = training_cycles
        self.needle_index = random.randrange(input_size)
        self.needle_value = None

    @property
    def is_dynamic(self):
        return False
        
    def get_possible_actions(self):
        return self.possible_actions
    
    def reset(self):
        self.remaining_cycles = self.initial_training_cycles
        self.needle_index = random.randrange(self.input_size)
        
    def more(self):
        return self.remaining_cycles > 0
    
    def sense(self):
        haystack = BitString.random(self.input_size)
        self.needle_value = haystack[self.needle_index]
        return haystack
    
    def execute(self, action):
        self.remaining_cycles -= 1
        return action == self.needle_value<jupyter_output><empty_output><jupyter_text>We have now defined all of the methods that `Scenario` requires. Let's give it a test run.<jupyter_code>import logging
import xcs

from xcs.scenarios import ScenarioObserver

# Setup logging so we can see the test run as it progresses.
logging.root.setLevel(logging.INFO)

# Create the scenario instance
problem = HaystackProblem()

# Wrap the scenario instance in an observer so progress gets logged,
# and pass it on to the test() function.
xcs.test(scenario=ScenarioObserver(problem))<jupyter_output><empty_output><jupyter_text>```INFO:xcs.scenarios:Possible actions:INFO:xcs.scenarios:    FalseINFO:xcs.scenarios:    TrueINFO:xcs.scenarios:Steps completed: 0INFO:xcs.scenarios:Average reward per step: 0.00000INFO:xcs.scenarios:Steps completed: 100INFO:xcs.scenarios:Average reward per step: 0.55000...INFO:xcs.scenarios:Steps completed: 900INFO:xcs.scenarios:Average reward per step: 0.51667INFO:xcs.scenarios:Steps completed: 1000INFO:xcs.scenarios:Average reward per step: 0.50900INFO:xcs.scenarios:Run completed.INFO:xcs.scenarios:Total steps: 1000INFO:xcs.scenarios:Total reward received: 509.00000INFO:xcs.scenarios:Average reward per step: 0.50900INFO:xcs:Classifiers:0101111000101101001001100110111110000110101110100110111001011110111001110101110010110100110010100010001101100000101111110101001101101110110111010110000010101110000101011000000110010110011010001001000111011110010100111110111101101110101011001110110011111101010011000101001101000011101011100111101001101000110010010100011111010100001010100111000 => False    Time Stamp: 169    Average Reward: 0.0    Error: 0.0    Fitness: 0.15000850000000002    Experience: 1    Action Set Size: 1.0    Numerosity: 111101111101001001111100011010100111000101101011000010011110110111000101001011101000100010110011111110111100100101010101001001000111010010110111100011101011000111111011011100110000001110111001001011111011001101100000100011011010000010111011000100100000000100111001101111100011101001111110011011011010110111001000010110101100001010001110110 => False    Time Stamp: 254    Average Reward: 0.0    Error: 0.0    Fitness: 0.15000850000000002    Experience: 1    Action Set Size: 1.0    Numerosity: 1...10010010010110101000100000011110010101110001101000111100101010010101111001001100011001011100111111000010011101001010011010010101100010110101111100010001100011101110011011101010110100011101001010110111111001100010011110111111110101001100111000111100010001011000000110100010010111110011011111101011010000110011010011110011011111010100101110 => True    Time Stamp: 996    Average Reward: 1.0    Error: 0.0    Fitness: 0.15000850000000002    Experience: 1    Action Set Size: 1.0    Numerosity: 1010100101000111111000010010101110011110011011110011110010111101011110000101100110001010111111001011001100011111010101110000111011001001010110011011011111110110111001100011111100110110011001110001000000010100100110111001000011000001010010100111100111001100100010001110011011101000000010001010011110001011001101111010101100100010010110000110101000 => True    Time Stamp: 998    Average Reward: 1.0    Error: 0.0    Fitness: 0.15000850000000002    Experience: 1    Action Set Size: 1.0    Numerosity: 1INFO:xcs:Total time: 2.65542 seconds``` Hmm, the classifier set didn't do so hot. Maybe we've found a weakness in the algorithm, or maybe some different parameter settings will improve its performance. Let's reduce the size of the haystack and give it more reward cycles so we can see whether it's learning at all.<jupyter_code>problem = HaystackProblem(training_cycles=10000, input_size=100)

xcs.test(scenario=ScenarioObserver(problem))<jupyter_output><empty_output><jupyter_text>```INFO:xcs.scenarios:Possible actions:INFO:xcs.scenarios:    FalseINFO:xcs.scenarios:    TrueINFO:xcs.scenarios:Steps completed: 0INFO:xcs.scenarios:Average reward per step: 0.00000INFO:xcs.scenarios:Steps completed: 100INFO:xcs.scenarios:Average reward per step: 0.47000...INFO:xcs.scenarios:Steps completed: 9900INFO:xcs.scenarios:Average reward per step: 0.49222INFO:xcs.scenarios:Steps completed: 10000INFO:xcs.scenarios:Average reward per step: 0.49210INFO:xcs.scenarios:Run completed.INFO:xcs.scenarios:Total steps: 10000INFO:xcs.scenarios:Total reward received: 4921.00000INFO:xcs.scenarios:Average reward per step: 0.49210INFO:xcs:Classifiers:11100101100001010010101111111110000100111001011011011011001011111001 => False    Time Stamp: 9771    Average Reward: 1.0    Error: 0.0    Fitness: 8.5e-07    Experience: 0    Action Set Size: 1    Numerosity: 1000011001010011111010010010101110100111101011111100111101000010000111000 => False    Time Stamp: 8972    Average Reward: 0.0    Error: 0.0    Fitness: 0.15000850000000002    Experience: 1    Action Set Size: 1.0    Numerosity: 1...1000010010010011010001101011001011101111110000100011110001011100101 => True    Time Stamp: 9993    Average Reward: 1.0    Error: 0.0    Fitness: 0.15000850000000002    Experience: 1    Action Set Size: 1.0    Numerosity: 1101001100000101001000011101001111110001100010000111011100011000 => False    Time Stamp: 9997    Average Reward: 1.0    Error: 0.0    Fitness: 0.15000850000000002    Experience: 1    Action Set Size: 1.0    Numerosity: 1INFO:xcs:Total time: 21.50882 seconds``` It appears the algorithm isn't learning at all, at least not at a visible rate. But after a few rounds of playing with the parameter values, it becomes apparent that with the correct settings and sufficient training cycles, it is possible for the algorithm to handle the new scenario.<jupyter_code>problem = HaystackProblem(training_cycles=10000, input_size=500)

algorithm = xcs.XCSAlgorithm()

# Default parameter settings in test()
algorithm.exploration_probability = .1

# Modified parameter settings
algorithm.ga_threshold = 1
algorithm.crossover_probability = .5
algorithm.do_action_set_subsumption = True
algorithm.do_ga_subsumption = False
algorithm.wildcard_probability = .998
algorithm.deletion_threshold = 1
algorithm.mutation_probability = .002

xcs.test(algorithm, scenario=ScenarioObserver(problem))<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.061238430274258, 'fraction_numerical': 0.08490727142320434, 'mean_word_length': 4.956061838893409, 'pattern_counts': {'":': 0, '<': 82, '<?xml version=': 0, '>': 112, 'https://': 5, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 14, 'word_list_counts': {'cursed_substrings.json': 5, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '20', 'dir': 'jupyter-structured-clean-dedup', 'id': '230342', 'license': 'BSD-3-Clause', 'n_tokens_mistral': 9707, 'n_tokens_neox': 7735, 'n_words': 3303, 'path': 'doc/XCSTutorial.ipynb', 'repo_name': 'cbtang01/SOC-LCS', 'size': '29292'}