aboutsummaryrefslogtreecommitdiffstats
path: root/src/jcgp/backend/modules/problem/TestCaseProblem.java
blob: 964860c043ee960b615f1fc57cca33007ee9c185 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
package jcgp.backend.modules.problem;

import java.io.File;

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import jcgp.backend.parsers.TestCaseParser;
import jcgp.backend.population.Population;
import jcgp.backend.resources.ModifiableResources;
import jcgp.backend.resources.Resources;

/**
 * Abstract model for a problem that uses test cases. A test case
 * problem is any problem that compares the chromosome output to
 * an expected output taken from a table of input-output mappings.
 * <br><br>
 * This class defines a basic data type for storing test cases, 
 * TestCase, and provides core functionality to add and manipulate
 * test cases in the problem. A subclass of {@code TestCaseProblem}
 * must simply override {@code parseTestCase()} to convert parsed
 * problem data strings into the required data type (T).
 * 
 * @see Problem
 * @author Eduardo Pedroni
 * @param <T> the data type to be used by the TestCaseProblem.
 */
public abstract class TestCaseProblem<T> extends Problem {
	
	/**
	 * Basic data type for encapsulating test cases, it simply
	 * contains arrays of inputs and outputs and associated getters.
	 * 
	 * @author Eduardo Pedroni
	 * @param <U> the data type of the test case.
	 */
	public static class TestCase<U> {
		private U[] inputs;
		private U[] outputs;
		
		/**
		 * Creates a new test case, inputs and outputs
		 * must be specified upon instantiation.
		 * 
		 * @param inputs the array of inputs.
		 * @param outputs the array of outputs.
		 */
		public TestCase(U[] inputs, U[] outputs) {
			this.inputs = inputs;
			this.outputs = outputs;
		}
		
		/**
		 * @return the complete array of inputs.
		 */
		public U[] getInputs() {
			return inputs;
		}
		
		/**
		 * @return the complete array of outputs.
		 */
		public U[] getOutputs() {
			return outputs;
		}
	}
	
	protected ObservableList<TestCase<T>> testCases;
	
	/**
	 * Creates a new TestCaseProblem object.
	 * 
	 * @param resources a reference to the experiment's resources.
	 */
	public TestCaseProblem(Resources resources) {
		super(resources);
		testCases = FXCollections.observableArrayList();
	}
	
	/**
	 * For internal use only, this method computes and returns the maximum fitness
	 * based on the number of test cases. Subclasses should override this method
	 * as necessary.
	 * 
	 * @return the maximum fitness based on number of test cases.
	 */
	protected double getMaxFitness() {
		int fitness = 0;
		for (TestCase<T> tc : testCases) {
			fitness += tc.getOutputs().length;
		}
		return fitness;
	}
	
	/**
	 * @return a list containing the test cases.
	 */
	public ObservableList<TestCase<T>> getTestCases() {
		return testCases;
	}
	
	/**
	 * This method is used internally by {@code addTestCase()} in order
	 * to appropriately parse strings into the right data type for the 
	 * test cases. Since the data type is problem-dependent, subclasses must 
	 * implement this method. This method must return a built {@code TestCase}
	 * object from the arguments given.
	 * 
	 * @param inputs the inputs represented as strings.
	 * @param outputs the outputs represented as strings.
	 * @return the parsed test case.
	 */
	protected abstract TestCase<T> parseTestCase(String[] inputs, String[] outputs);
	
	/**
	 * Adds test cases to the problem instance as they get parsed from a 
	 * problem data file. This template method uses {@code parseTestCase}, which
	 * must be implemented by subclasses.
	 * 
	 * @param inputs the inputs represented as strings.
	 * @param outputs the outputs represented as strings.
	 */
	public final void addTestCase(String[] inputs, String[] outputs) {
		TestCase<T> testCase = parseTestCase(inputs, outputs);
		
		if (testCase.getInputs().length != getResources().inputs()) {
			throw new IllegalArgumentException("Received test case with " + testCase.getInputs().length + 
					" inputs but need exactly " + getResources().inputs());
		} else if (testCase.getOutputs().length != getResources().outputs()) {
			throw new IllegalArgumentException("Received test case with " + testCase.getOutputs().length + 
					" outputs but need exactly " + getResources().outputs());
		} else {
			this.testCases.add(testCase);
			maxFitness.set(getMaxFitness());
		}
	}

	/**
	 * Remove all test cases.
	 */
	public void clearTestCases() {
		testCases.clear();
		maxFitness.set(getMaxFitness());
	}

	@Override
	public void parseProblemData(File file, ModifiableResources resources) {
		// use standard test case parser for this
		TestCaseParser.parse(file, this, resources);
	}
	
	@Override
	public int hasImprovement(Population population) {
		for (int i = 0; i < getResources().populationSize(); i++) {
			if (getFitnessOrientation() == BestFitness.HIGH) {
				if (population.get(i).getFitness() > bestFitness.get()) {
					bestFitness.set(population.get(i).getFitness());
					return i;
				}
			} else {
				if (population.get(i).getFitness() < bestFitness.get()) {
					bestFitness.set(population.get(i).getFitness());
					return i;
				}
			}
		}
		return -1;
	}
}