1 | /* |
---|
2 | Copyright 2006 by Sean Luke and George Mason University |
---|
3 | Licensed under the Academic Free License version 3.0 |
---|
4 | See the file "LICENSE" for more information |
---|
5 | */ |
---|
6 | |
---|
7 | |
---|
8 | package ec.eval; |
---|
9 | |
---|
10 | import ec.*; |
---|
11 | import ec.util.*; |
---|
12 | import ec.coevolve.GroupedProblemForm; |
---|
13 | import ec.simple.SimpleProblemForm; |
---|
14 | import ec.steadystate.QueueIndividual; |
---|
15 | import java.util.ArrayList; |
---|
16 | |
---|
17 | import java.io.*; |
---|
18 | |
---|
19 | /** |
---|
20 | * MasterProblem.java |
---|
21 | * |
---|
22 | |
---|
23 | <p>The MasterProblem is a special ECJ problem that performs evaluations by sending them to |
---|
24 | a remote Slave process to be evaluated. As it implements both the |
---|
25 | <i>SimpleProblemForm</i> and the <i>GroupedProblemForm</i> interfaces, the MasterProblem |
---|
26 | can perform both traditional EC evaluations as well as coevolutionary evaluations. |
---|
27 | |
---|
28 | <p>When a MasterProblem is specified by the Evaluator, the Problem is set up as usual, but then |
---|
29 | the MasterProblem replaces it. The Problem is not garbage collected -- instead, it's hung off the |
---|
30 | MasterProblem's <tt>problem</tt> variable. In some sense the Problem is "pushed aside". |
---|
31 | |
---|
32 | <p>If the Evaluator begins by calling prepareToEvaluate(), and we're not doing coevolution, then |
---|
33 | the MasterProblem does not evaluate individuals immediately. Instead, it waits for at most |
---|
34 | <i>jobSize</i> individuals be submitted via evaluate(), and then sends them all off in a group, |
---|
35 | called a <i>job</i>, to the remote slave. In other situations (coevolution, or no prepareToEvaluate()) |
---|
36 | the MasterProblem sends off individuals immediately. |
---|
37 | |
---|
38 | <p>It may be the case that no Slave has space in its queue to accept a new job containing, among others, |
---|
39 | your new individual. In this case, calling evaluate() will block until one comes available. You can avoid |
---|
40 | this by testing for availability first by calling canEvaluate(). Note that canEvaluate() and evaluate() |
---|
41 | together are not atomic and so you should not rely on this facility if your system uses multiple threads. |
---|
42 | |
---|
43 | <P>When the individuals or their fitnesses return, they are immediately updated in place. You have three |
---|
44 | options to wait for them: |
---|
45 | |
---|
46 | <ul> |
---|
47 | <li><p>You can wait for all the individuals to finish evaluation by calling finishEvaluating(). |
---|
48 | If you call this method before a job is entirely filled, it will be sent in truncated format (which |
---|
49 | generally is perfectly fine). You then block until all the jobs have been completed and the individuals |
---|
50 | updated. |
---|
51 | |
---|
52 | <li><p>You can block until at least one individual is available, by calling getNextEvaluatedIndividual(), |
---|
53 | which blocks and then returns the individual that was just completed. |
---|
54 | |
---|
55 | <li><p>You can test in non-blocking fashion to see if an individual is available, by calling |
---|
56 | evaluatedIndividualAvailable(). If this returns true, you may then call getNextEvaluatedIndividual() |
---|
57 | to get the individual. Note that this isn't atomic, so don't use it if you have multiple threads. |
---|
58 | </ul> |
---|
59 | |
---|
60 | <p><b>Parameters</b><br> |
---|
61 | <table> |
---|
62 | <tr><td valign=top><i>base.</i><tt>debug-info</tt><br> |
---|
63 | <font size=-1>boolean</font></td> |
---|
64 | <td valign=top>(whether the system should display information useful for debugging purposes)<br> |
---|
65 | |
---|
66 | <tr><td valign=top><i>base.</i><tt>job-size</tt><br> |
---|
67 | <font size=-1>integer > 0 </font></td> |
---|
68 | <td valign=top>(how large should a job be at most?)<br> |
---|
69 | </td></tr> |
---|
70 | |
---|
71 | |
---|
72 | <!-- technically these are handled by the SlaveMonitor --> |
---|
73 | |
---|
74 | <tr><td valign=top><tt>eval.master.port</tt><br> |
---|
75 | <font size=-1>int</font></td> |
---|
76 | <td valign=top>(the port where the slaves will connect)<br> |
---|
77 | </td></tr> |
---|
78 | <tr><td valign=top><tt>eval.compression</tt><br> |
---|
79 | <font size=-1>boolean</font></td> |
---|
80 | <td valign=top>(whether the communication with the slaves should be compressed or not)<br> |
---|
81 | </td></tr> |
---|
82 | <tr><td valign=top><tt>eval.masterproblem.max-jobs-per-slave</tt><br> |
---|
83 | <font size=-1>int</font></td> |
---|
84 | <td valign=top>(the maximum load (number of jobs) per slave at any point in time)<br> |
---|
85 | </td></tr> |
---|
86 | |
---|
87 | </table> |
---|
88 | |
---|
89 | * @author Liviu Panait, Keith Sullivan, and Sean Luke |
---|
90 | * @version 1.0 |
---|
91 | */ |
---|
92 | |
---|
93 | public class MasterProblem extends Problem implements SimpleProblemForm, GroupedProblemForm |
---|
94 | { |
---|
95 | public static final String P_DEBUG_INFO = "debug-info"; |
---|
96 | public static final String P_JOB_SIZE = "job-size"; |
---|
97 | |
---|
98 | int jobSize; |
---|
99 | boolean showDebugInfo; |
---|
100 | public Problem problem; |
---|
101 | public boolean batchMode; |
---|
102 | public SlaveMonitor monitor; |
---|
103 | |
---|
104 | // except for the problem, everything else is shallow-cloned |
---|
105 | public Object clone() |
---|
106 | { |
---|
107 | MasterProblem c = (MasterProblem)(super.clone()); |
---|
108 | |
---|
109 | // shallow-cloned stuff |
---|
110 | c.monitor = monitor; |
---|
111 | c.batchMode = batchMode; |
---|
112 | c.jobSize = jobSize; |
---|
113 | c.showDebugInfo = showDebugInfo; |
---|
114 | |
---|
115 | // deep-cloned stuff |
---|
116 | c.problem = (Problem)(problem.clone()); |
---|
117 | |
---|
118 | return c; |
---|
119 | } |
---|
120 | |
---|
121 | // setup |
---|
122 | public void setup(final EvolutionState state, final Parameter base) |
---|
123 | { |
---|
124 | Thread.currentThread().setName("MainThread: "); |
---|
125 | super.setup(state, base); |
---|
126 | showDebugInfo = state.parameters.getBoolean(base.push(P_DEBUG_INFO),null,false); |
---|
127 | |
---|
128 | jobSize = state.parameters.getIntWithDefault(base.push(P_JOB_SIZE),null,1); |
---|
129 | if (jobSize<=0) |
---|
130 | state.output.fatal("The job size must be an integer > 0.", base.push(P_JOB_SIZE)); |
---|
131 | |
---|
132 | batchMode = false; |
---|
133 | } |
---|
134 | |
---|
135 | // prepare for a batch of evaluations |
---|
136 | public void prepareToEvaluate(final EvolutionState state, final int threadnum) |
---|
137 | { |
---|
138 | if (jobSize > 1) queue = new ArrayList(); |
---|
139 | batchMode = true; |
---|
140 | } |
---|
141 | |
---|
142 | // wait until a batch of evaluations is finished |
---|
143 | public void finishEvaluating(final EvolutionState state, final int threadnum) |
---|
144 | { |
---|
145 | if(showDebugInfo) |
---|
146 | state.output.message(Thread.currentThread().getName() + "Waiting for all slaves to finish."); |
---|
147 | flush(state, threadnum); |
---|
148 | queue = null; // get rid of it just in case |
---|
149 | |
---|
150 | monitor.waitForAllSlavesToFinishEvaluating( state ); |
---|
151 | batchMode = false; |
---|
152 | if(showDebugInfo) |
---|
153 | state.output.message(Thread.currentThread().getName() + "All slaves have finished their jobs."); |
---|
154 | } |
---|
155 | |
---|
156 | // evaluate a regular individual |
---|
157 | public void evaluate(EvolutionState state, Individual ind, int subpopulation, int threadnum) |
---|
158 | { |
---|
159 | if (jobSize > 1 && batchMode == true) // chunked evaluation mechanism |
---|
160 | { |
---|
161 | queue.add(new QueueIndividual(ind, subpopulation)); |
---|
162 | if (queue.size() >= jobSize) |
---|
163 | flush(state, threadnum); |
---|
164 | } |
---|
165 | else /// ordinary evaluation mechanism |
---|
166 | evaluate(state, new Individual[] { ind }, new int[] { subpopulation }, threadnum); |
---|
167 | } |
---|
168 | |
---|
169 | |
---|
170 | ArrayList queue; |
---|
171 | void flush(EvolutionState state, int threadnum) |
---|
172 | { |
---|
173 | int subpopulation; |
---|
174 | if (queue!=null && queue.size() > 0 ) |
---|
175 | { |
---|
176 | Individual[] inds = new Individual[queue.size()]; |
---|
177 | int[] subpopulations = new int[queue.size()]; |
---|
178 | for(int i = 0; i < queue.size(); i++) |
---|
179 | { |
---|
180 | QueueIndividual qind = (QueueIndividual)(queue.get(i)); |
---|
181 | inds[i] = qind.ind; |
---|
182 | subpopulations[i] = qind.subpop; |
---|
183 | } |
---|
184 | evaluate(state, inds, subpopulations, threadnum); |
---|
185 | } |
---|
186 | queue = new ArrayList(); |
---|
187 | } |
---|
188 | |
---|
189 | |
---|
190 | // send a group of individuals to one slave for evaluation |
---|
191 | void evaluate(EvolutionState state, Individual inds[], int[] subpopulations, int threadnum) |
---|
192 | { |
---|
193 | if(showDebugInfo) |
---|
194 | state.output.message(Thread.currentThread().getName() + "Starting a " + (batchMode ? "batched " : "") + "SimpleProblemForm evaluation."); |
---|
195 | |
---|
196 | // Acquire a slave socket |
---|
197 | Job job = new Job(); |
---|
198 | job.type = Slave.V_EVALUATESIMPLE; |
---|
199 | job.inds = inds; |
---|
200 | job.subPops = subpopulations ; |
---|
201 | job.updateFitness = new boolean[inds.length]; |
---|
202 | for (int i=0 ; i < inds.length; i++) |
---|
203 | job.updateFitness[i]=true; |
---|
204 | monitor.scheduleJobForEvaluation(state,job); |
---|
205 | if( !batchMode ) |
---|
206 | monitor.waitForAllSlavesToFinishEvaluating( state ); |
---|
207 | if(showDebugInfo) state.output.message(Thread.currentThread().getName() + "Finished a " + (batchMode ? "batched " : "") + "SimpleProblemForm evaluation."); |
---|
208 | } |
---|
209 | |
---|
210 | |
---|
211 | |
---|
212 | |
---|
213 | /* (non-Javadoc) |
---|
214 | * @see ec.simple.SimpleProblemForm#describe(ec.EvolutionState, ec.Individual, int, int) |
---|
215 | */ |
---|
216 | public void describe(EvolutionState state, Individual ind, int subpopulation, int threadnum, int log) |
---|
217 | { |
---|
218 | if ((problem instanceof SimpleProblemForm)) |
---|
219 | { |
---|
220 | ((SimpleProblemForm)problem).describe(state, ind, subpopulation, threadnum, log); |
---|
221 | } |
---|
222 | } |
---|
223 | |
---|
224 | /* (non-Javadoc) |
---|
225 | * @see ec.coevolve.GroupedProblemForm#preprocessPopulation(ec.EvolutionState, ec.Population) |
---|
226 | */ |
---|
227 | public void preprocessPopulation(EvolutionState state, Population pop, boolean countVictoriesOnly) |
---|
228 | { |
---|
229 | if (!(problem instanceof GroupedProblemForm)) |
---|
230 | { |
---|
231 | state.output.fatal("MasterProblem.preprocessPopulation(...) invoked, but the underlying Problem is not of GroupedProblemForm"); |
---|
232 | } |
---|
233 | |
---|
234 | ((GroupedProblemForm) problem).preprocessPopulation(state, pop, countVictoriesOnly); |
---|
235 | } |
---|
236 | |
---|
237 | /* (non-Javadoc) |
---|
238 | * @see ec.coevolve.GroupedProblemForm#postprocessPopulation(ec.EvolutionState, ec.Population) |
---|
239 | */ |
---|
240 | public void postprocessPopulation(EvolutionState state, Population pop, boolean countVictoriesOnly) |
---|
241 | { |
---|
242 | if (!(problem instanceof GroupedProblemForm)) |
---|
243 | { |
---|
244 | state.output.fatal("MasterProblem.postprocessPopulation(...) invoked, but the underlying Problem is not of GroupedProblemForm"); |
---|
245 | } |
---|
246 | |
---|
247 | ((GroupedProblemForm) problem).postprocessPopulation(state, pop, countVictoriesOnly); |
---|
248 | } |
---|
249 | |
---|
250 | // regular coevolutionary evaluation |
---|
251 | public void evaluate(EvolutionState state, Individual[] inds, |
---|
252 | boolean[] updateFitness, boolean countVictoriesOnly, int[] subpops, int threadnum) |
---|
253 | { |
---|
254 | if(showDebugInfo) |
---|
255 | state.output.message("Starting a GroupedProblemForm evaluation."); |
---|
256 | |
---|
257 | // Acquire a slave socket |
---|
258 | Job job = new Job(); |
---|
259 | job.type = Slave.V_EVALUATEGROUPED; |
---|
260 | job.subPops = subpops; |
---|
261 | job.countVictoriesOnly = countVictoriesOnly; |
---|
262 | job.inds = inds; |
---|
263 | job.updateFitness = updateFitness; |
---|
264 | monitor.scheduleJobForEvaluation(state,job); |
---|
265 | |
---|
266 | if( !batchMode ) |
---|
267 | monitor.waitForAllSlavesToFinishEvaluating( state ); |
---|
268 | |
---|
269 | if(showDebugInfo) |
---|
270 | state.output.message("Finished the GroupedProblemForm evaluation."); |
---|
271 | } |
---|
272 | |
---|
273 | /** Custom serialization */ |
---|
274 | private void writeObject(ObjectOutputStream out) throws IOException |
---|
275 | { |
---|
276 | out.writeObject(problem); |
---|
277 | } |
---|
278 | |
---|
279 | /** Custom serialization */ |
---|
280 | private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException |
---|
281 | { |
---|
282 | problem = (Problem) in.readObject(); |
---|
283 | } |
---|
284 | |
---|
285 | /** Initialize contacts with the slaves */ |
---|
286 | public void initializeContacts( final EvolutionState state ) |
---|
287 | { |
---|
288 | if(showDebugInfo) |
---|
289 | state.output.message(Thread.currentThread().getName() + "Spawning the server thread."); |
---|
290 | monitor = new SlaveMonitor(state, showDebugInfo); |
---|
291 | } |
---|
292 | |
---|
293 | /** Reinitialize contacts with the slaves */ |
---|
294 | public void reinitializeContacts( final EvolutionState state ) |
---|
295 | { |
---|
296 | initializeContacts(state); |
---|
297 | } |
---|
298 | |
---|
299 | /** Gracefully close contacts with the slaves */ |
---|
300 | public void closeContacts(EvolutionState state, int result) |
---|
301 | { |
---|
302 | monitor.shutdown(); |
---|
303 | } |
---|
304 | |
---|
305 | public boolean canEvaluate() |
---|
306 | { |
---|
307 | return (monitor.numAvailableSlaves() != 0); |
---|
308 | } |
---|
309 | |
---|
310 | /** This will only return true if (1) the EvolutionState is a SteadyStateEvolutionState and |
---|
311 | (2) an individual has returned from the system. If you're not doing steady state evolution, |
---|
312 | you should not call this method. */ |
---|
313 | public boolean evaluatedIndividualAvailable() |
---|
314 | { |
---|
315 | return monitor.evaluatedIndividualAvailable(); |
---|
316 | } |
---|
317 | |
---|
318 | /** This method blocks until an individual is available from the slaves (which will cause evaluatedIndividualAvailable() |
---|
319 | to return true), at which time it returns the individual. You should only call this method |
---|
320 | if you're doing steady state evolution -- otherwise, the method will block forever. */ |
---|
321 | public QueueIndividual getNextEvaluatedIndividual() |
---|
322 | { |
---|
323 | return monitor.waitForIndividual(); |
---|
324 | } |
---|
325 | } |
---|