1 package net.sf.openrocket.optimization.general;
3 import java.util.ArrayList;
4 import java.util.Collection;
5 import java.util.HashMap;
6 import java.util.Iterator;
9 import java.util.concurrent.Callable;
10 import java.util.concurrent.ExecutionException;
11 import java.util.concurrent.ExecutorService;
12 import java.util.concurrent.Future;
13 import java.util.concurrent.LinkedBlockingQueue;
14 import java.util.concurrent.ThreadFactory;
15 import java.util.concurrent.ThreadPoolExecutor;
16 import java.util.concurrent.TimeUnit;
18 import net.sf.openrocket.util.BugException;
21 * An implementation of a ParallelFunctionCache that evaluates function values
22 * in parallel and caches them. This allows pre-calculating possibly required
23 * function values beforehand. If values are not required after all, the
24 * computation can be aborted assuming the function evaluation supports it.
26 * Note that while this class handles threads and abstracts background execution,
27 * the public methods themselves are NOT thread-safe and should be called from
28 * only one thread at a time.
30 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
32 public class ParallelExecutorCache implements ParallelFunctionCache {
34 private final Map<Point, Double> functionCache = new HashMap<Point, Double>();
35 private final Map<Point, Future<Double>> futureMap = new HashMap<Point, Future<Double>>();
37 private ExecutorService executor;
39 private Function function;
43 * Construct a cache that uses the same number of computational threads as there are
44 * processors available.
46 public ParallelExecutorCache() {
47 this(Runtime.getRuntime().availableProcessors());
51 * Construct a cache that uses the specified number of computational threads for background
52 * computation. The threads that are created are marked as daemon threads.
54 * @param threadCount the number of threads to use in the executor.
56 public ParallelExecutorCache(int threadCount) {
57 this(new ThreadPoolExecutor(threadCount, threadCount, 60, TimeUnit.SECONDS,
58 new LinkedBlockingQueue<Runnable>(),
61 public Thread newThread(Runnable r) {
62 Thread t = new Thread(r);
70 * Construct a cache that uses the specified ExecutorService for managing
71 * computational threads.
73 * @param executor the executor to use for function evaluations.
75 public ParallelExecutorCache(ExecutorService executor) {
76 this.executor = executor;
82 public void compute(Collection<Point> points) {
83 for (Point p : points) {
90 public void compute(Point point) {
92 if (isOutsideRange(point)) {
93 // Point is outside of range
97 if (functionCache.containsKey(point)) {
98 // Function has already been evaluated at the point
102 if (futureMap.containsKey(point)) {
103 // Function is being evaluated at the point
107 // Submit point for evaluation
108 FunctionCallable callable = new FunctionCallable(function, point);
109 Future<Double> future = executor.submit(callable);
110 futureMap.put(point, future);
115 public void waitFor(Collection<Point> points) throws InterruptedException, OptimizationException {
116 for (Point p : points) {
123 public void waitFor(Point point) throws InterruptedException, OptimizationException {
124 if (isOutsideRange(point)) {
128 if (functionCache.containsKey(point)) {
132 Future<Double> future = futureMap.get(point);
133 if (future == null) {
134 throw new IllegalStateException("waitFor called for " + point + " but it is not being computed");
138 double value = future.get();
139 functionCache.put(point, value);
140 } catch (ExecutionException e) {
141 Throwable cause = e.getCause();
142 if (cause instanceof InterruptedException) {
143 throw (InterruptedException) cause;
145 if (cause instanceof OptimizationException) {
146 throw (OptimizationException) cause;
148 if (cause instanceof RuntimeException) {
149 throw (RuntimeException) cause;
152 throw new BugException("Function threw unknown exception while processing", e);
159 public List<Point> abort(Collection<Point> points) {
160 List<Point> computed = new ArrayList<Point>(Math.min(points.size(), 10));
162 for (Point p : points) {
174 public boolean abort(Point point) {
175 if (isOutsideRange(point)) {
179 if (functionCache.containsKey(point)) {
183 Future<Double> future = futureMap.remove(point);
184 if (future == null) {
185 throw new IllegalStateException("abort called for " + point + " but it is not being computed");
188 if (future.isDone()) {
189 // Evaluation has been completed, store value in cache
191 double value = future.get();
192 functionCache.put(point, value);
194 } catch (Exception e) {
198 // Cancel the evaluation
206 public void abortAll() {
207 Iterator<Point> iterator = futureMap.keySet().iterator();
208 while (iterator.hasNext()) {
209 Point point = iterator.next();
210 Future<Double> future = futureMap.get(point);
213 if (future.isDone()) {
214 // Evaluation has been completed, store value in cache
216 double value = future.get();
217 functionCache.put(point, value);
218 } catch (Exception e) {
222 // Cancel the evaluation
230 public double getValue(Point point) {
231 if (isOutsideRange(point)) {
232 return Double.MAX_VALUE;
235 Double d = functionCache.get(point);
237 throw new IllegalStateException(point + " is not in function cache. " +
238 "functionCache=" + functionCache + " futureMap=" + futureMap);
245 public Function getFunction() {
250 public void setFunction(Function function) {
251 this.function = function;
256 public void clearCache() {
257 List<Point> list = new ArrayList<Point>(futureMap.keySet());
259 functionCache.clear();
263 public ExecutorService getExecutor() {
269 * Check whether a point is outside of the valid optimization range.
271 private boolean isOutsideRange(Point p) {
273 for (int i = 0; i < n; i++) {
275 // Include NaN in disallowed range
276 if (!(d >= 0.0 && d <= 1.0)) {
285 * A Callable that evaluates a function at a specific point and returns the result.
287 private class FunctionCallable implements Callable<Double> {
288 private final Function calledFunction;
289 private final Point point;
291 public FunctionCallable(Function function, Point point) {
292 this.calledFunction = function;
297 public Double call() throws InterruptedException, OptimizationException {
298 return calledFunction.evaluate(point);