1 package net.sf.openrocket.optimization.general;
3 import java.util.ArrayList;
4 import java.util.Collection;
5 import java.util.HashMap;
8 import java.util.concurrent.Callable;
9 import java.util.concurrent.ExecutionException;
10 import java.util.concurrent.ExecutorService;
11 import java.util.concurrent.Future;
12 import java.util.concurrent.LinkedBlockingQueue;
13 import java.util.concurrent.ThreadFactory;
14 import java.util.concurrent.ThreadPoolExecutor;
15 import java.util.concurrent.TimeUnit;
17 import net.sf.openrocket.util.BugException;
20 * An implementation of a ParallelFunctionCache that evaluates function values
21 * in parallel and caches them. This allows pre-calculating possibly required
22 * function values beforehand. If values are not required after all, the
23 * computation can be aborted assuming the function evaluation supports it.
25 * Note that while this class handles threads and abstracts background execution,
26 * the public methods themselves are NOT thread-safe and should be called from
27 * only one thread at a time.
29 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
31 public class ParallelExecutorCache implements ParallelFunctionCache {
33 private final Map<Point, Double> functionCache = new HashMap<Point, Double>();
34 private final Map<Point, Future<Double>> futureMap = new HashMap<Point, Future<Double>>();
36 private ExecutorService executor;
38 private Function function;
42 * Construct a cache that uses the same number of computational threads as there are
43 * processors available.
45 public ParallelExecutorCache() {
46 this(Runtime.getRuntime().availableProcessors());
50 * Construct a cache that uses the specified number of computational threads for background
51 * computation. The threads that are created are marked as daemon threads.
53 * @param threadCount the number of threads to use in the executor.
55 public ParallelExecutorCache(int threadCount) {
56 this(new ThreadPoolExecutor(threadCount, threadCount, 60, TimeUnit.SECONDS,
57 new LinkedBlockingQueue<Runnable>(),
60 public Thread newThread(Runnable r) {
61 Thread t = new Thread(r);
69 * Construct a cache that uses the specified ExecutorService for managing
70 * computational threads.
72 * @param executor the executor to use for function evaluations.
74 public ParallelExecutorCache(ExecutorService executor) {
75 this.executor = executor;
81 public void compute(Collection<Point> points) {
82 for (Point p : points) {
89 public void compute(Point point) {
91 if (isOutsideRange(point)) {
92 // Point is outside of range
96 if (functionCache.containsKey(point)) {
97 // Function has already been evaluated at the point
101 if (futureMap.containsKey(point)) {
102 // Function is being evaluated at the point
106 // Submit point for evaluation
107 FunctionCallable callable = new FunctionCallable(function, point);
108 Future<Double> future = executor.submit(callable);
109 futureMap.put(point, future);
114 public void waitFor(Collection<Point> points) throws InterruptedException, OptimizationException {
115 for (Point p : points) {
122 public void waitFor(Point point) throws InterruptedException, OptimizationException {
123 if (isOutsideRange(point)) {
127 if (functionCache.containsKey(point)) {
131 Future<Double> future = futureMap.get(point);
132 if (future == null) {
133 throw new IllegalStateException("waitFor called for " + point + " but it is not being computed");
137 double value = future.get();
138 functionCache.put(point, value);
139 } catch (ExecutionException e) {
140 Throwable cause = e.getCause();
141 if (cause instanceof InterruptedException) {
142 throw (InterruptedException) cause;
144 if (cause instanceof OptimizationException) {
145 throw (OptimizationException) cause;
147 if (cause instanceof RuntimeException) {
148 throw (RuntimeException) cause;
151 throw new BugException("Function threw unknown exception while processing", e);
158 public List<Point> abort(Collection<Point> points) {
159 List<Point> computed = new ArrayList<Point>(Math.min(points.size(), 10));
161 for (Point p : points) {
173 public boolean abort(Point point) {
174 if (isOutsideRange(point)) {
178 if (functionCache.containsKey(point)) {
182 Future<Double> future = futureMap.remove(point);
183 if (future == null) {
184 throw new IllegalStateException("abort called for " + point + " but it is not being computed");
187 if (future.isDone()) {
188 // Evaluation has been completed, store value in cache
190 double value = future.get();
191 functionCache.put(point, value);
193 } catch (Exception e) {
197 // Cancel the evaluation
205 public double getValue(Point point) {
206 if (isOutsideRange(point)) {
207 return Double.MAX_VALUE;
210 Double d = functionCache.get(point);
212 throw new IllegalStateException(point + " is not in function cache. " +
213 "functionCache=" + functionCache + " futureMap=" + futureMap);
220 public Function getFunction() {
225 public void setFunction(Function function) {
226 this.function = function;
231 public void clearCache() {
232 List<Point> list = new ArrayList<Point>(futureMap.keySet());
234 functionCache.clear();
238 public ExecutorService getExecutor() {
244 * Check whether a point is outside of the valid optimization range.
246 private boolean isOutsideRange(Point p) {
248 for (int i = 0; i < n; i++) {
250 // Include NaN in disallowed range
251 if (!(d >= 0.0 && d <= 1.0)) {
260 * A Callable that evaluates a function at a specific point and returns the result.
262 private class FunctionCallable implements Callable<Double> {
263 private final Function calledFunction;
264 private final Point point;
266 public FunctionCallable(Function function, Point point) {
267 this.calledFunction = function;
272 public Double call() throws InterruptedException, OptimizationException {
273 return calledFunction.evaluate(point);