package net.sf.openrocket.optimization.rocketoptimization; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import net.sf.openrocket.document.Simulation; import net.sf.openrocket.optimization.general.Function; import net.sf.openrocket.optimization.general.OptimizationException; import net.sf.openrocket.optimization.general.Point; import net.sf.openrocket.rocketcomponent.Rocket; import net.sf.openrocket.unit.UnitGroup; import net.sf.openrocket.unit.Value; import net.sf.openrocket.util.Pair; /** * A Function that optimizes a specific RocketOptimizationParameter to some goal * by modifying a base simulation using SimulationModifiers. * * @author Sampo Niskanen <sampo.niskanen@iki.fi> */ public class RocketOptimizationFunction implements Function { private static final Logger log = LoggerFactory.getLogger(RocketOptimizationFunction.class); private static final double OUTSIDE_DOMAIN_SCALE = 1.0e200; /* * NOTE: This class must be thread-safe!!! */ private final Simulation baseSimulation; private final OptimizableParameter parameter; private final OptimizationGoal goal; private final SimulationDomain domain; private final SimulationModifier[] modifiers; private final List<RocketOptimizationListener> listeners = new ArrayList<RocketOptimizationListener>(); /** * Sole constructor. * <p> * The dimensionality of the resulting function is the same as the length of the * modifiers array. * * @param baseSimulation the base simulation to modify * @param parameter the rocket parameter to optimize * @param goal the goal of the rocket parameter * @param modifiers the modifiers that modify the simulation */ public RocketOptimizationFunction(Simulation baseSimulation, OptimizableParameter parameter, OptimizationGoal goal, SimulationDomain domain, SimulationModifier... modifiers) { this.baseSimulation = baseSimulation; this.parameter = parameter; this.goal = goal; this.domain = domain; this.modifiers = modifiers.clone(); if (modifiers.length == 0) { throw new IllegalArgumentException("No SimulationModifiers specified"); } } @Override public double evaluate(Point point) throws InterruptedException, OptimizationException { /* * parameterValue is the computed parameter value (e.g. altitude) * goalValue is the value that needs to be minimized */ double goalValue, parameterValue; log.debug("Computing optimization function value at point " + point); // Create the new simulation based on the point double[] p = point.asArray(); if (p.length != modifiers.length) { throw new IllegalArgumentException("Point has length " + p.length + " while function has " + modifiers.length + " simulation modifiers"); } Simulation simulation = newSimulationInstance(baseSimulation); for (int i = 0; i < modifiers.length; i++) { modifiers[i].modify(simulation, p[i]); } // Check whether the point is within the simulation domain Pair<Double, Value> d = domain.getDistanceToDomain(simulation); double distance = d.getU(); Value referenceValue = d.getV(); if (distance > 0 || Double.isNaN(distance)) { if (Double.isNaN(distance)) { goalValue = Double.MAX_VALUE; } else { goalValue = (distance + 1) * OUTSIDE_DOMAIN_SCALE; } log.debug("Optimization point is outside of domain, distance=" + distance + " goal function value=" + goalValue); fireEvent(simulation, point, referenceValue, null, goalValue); return goalValue; } // Compute the optimization value parameterValue = parameter.computeValue(simulation); goalValue = goal.getMinimizationParameter(parameterValue); if (Double.isNaN(goalValue)) { log.warn("Computed goal value was NaN, baseSimulation=" + baseSimulation + " parameter=" + parameter + " goal=" + goal + " modifiers=" + Arrays.toString(modifiers) + " simulation=" + simulation + " parameter value=" + parameterValue); goalValue = Double.MAX_VALUE; } log.trace("Parameter value at point " + point + " is " + parameterValue + ", goal function value=" + goalValue); fireEvent(simulation, point, referenceValue, new Value(parameterValue, parameter.getUnitGroup().getDefaultUnit()), goalValue); return goalValue; } /** * Returns a new deep copy of the simulation and rocket. This methods performs * synchronization on the simulation for thread protection. * <p> * Note: This method is package-private for unit testing purposes. * * @return a new deep copy of the simulation and rocket */ Simulation newSimulationInstance(Simulation simulation) { synchronized (baseSimulation) { Rocket newRocket = simulation.getRocket().copyWithOriginalID(); Simulation newSimulation = simulation.duplicateSimulation(newRocket); return newSimulation; } } /** * Add a listener to this function. The listener will be notified each time the * function is successfully evaluated. * <p> * Note that the listener may be called from other threads and must be thread-safe! * * @param listener the listener to add. */ public void addRocketOptimizationListener(RocketOptimizationListener listener) { listeners.add(listener); } public void removeRocketOptimizationListener(RocketOptimizationListener listener) { listeners.remove(listener); } private void fireEvent(Simulation simulation, Point p, Value domainReference, Value parameterValue, double goalValue) throws OptimizationException { if (listeners.isEmpty()) { return; } Value[] values = new Value[p.dim()]; for (int i = 0; i < values.length; i++) { double value = modifiers[i].getCurrentSIValue(simulation); UnitGroup unit = modifiers[i].getUnitGroup(); values[i] = new Value(value, unit.getDefaultUnit()); } for (RocketOptimizationListener l : listeners) { l.evaluated(p, values, domainReference, parameterValue, goalValue); } } }