1 package net.sf.openrocket.simulation;
3 import java.util.SortedMap;
4 import java.util.TreeMap;
6 import net.sf.openrocket.document.Simulation;
7 import net.sf.openrocket.logging.LogHelper;
8 import net.sf.openrocket.startup.Application;
9 import net.sf.openrocket.unit.FixedUnitGroup;
10 import net.sf.openrocket.unit.UnitGroup;
11 import net.sf.openrocket.util.ArrayList;
12 import de.congrace.exp4j.Calculable;
13 import de.congrace.exp4j.ExpressionBuilder;
17 * Represents a single custom expression
18 * @author Richard Graham
21 public class CustomExpression implements Cloneable{
23 private static final LogHelper log = Application.getLogger();
25 private String name, symbol, unit, expression;
26 private ExpressionBuilder builder;
27 private Simulation sim = null;
29 // A map of available operator strings (keys) and description of function (value)
30 public static final SortedMap<String, String> AVAILABLE_OPERATORS = new TreeMap<String, String>() {{
31 put("+" , "Addition");
32 put("-" , "Subtraction");
33 put("*" , "Multiplication");
36 put("^" , "Exponentiation");
37 put("abs()" , "Absolute value");
38 put("ceil()" , "Ceiling (next integer value");
39 put("floor()" , "Floor (previous integer value");
40 put("sqrt()" , "Square root");
41 put("cbrt()" , "Cubic root");
42 put("exp()" , "Euler\'s number raised to the value (e^x)");
43 put("log()" , "Natural logarithm");
44 put("sin()" , "Sine");
45 put("cos()" , "Cosine");
46 put("tan()" , "Tangent");
47 put("asin()" , "Arc sine");
48 put("acos()" , "Arc cosine");
49 put("atan()" , "Arc tangent");
50 put("sinh()" , "Hyerbolic sine");
51 put("cosh()" , "Hyperbolic cosine");
52 put("tanh()" , "Hyperbolic tangent");
56 public CustomExpression(){
63 public CustomExpression(Simulation sim){
68 public CustomExpression(Simulation sim, String name, String symbol, String unit, String expression) {
73 setExpression(expression);
78 * Use this to update the simulation this is associated with
80 public void setSimulation(Simulation sim){
84 public Simulation getSimulation() {
89 * Returns the flight data branch 0 for this simulation, or an empty branch
90 * if no simulated data exists
92 private FlightDataBranch getBranch() {
93 if ( sim == null || sim.getSimulatedData() == null || sim.getSimulatedData().getBranchCount() == 0){
94 return new FlightDataBranch();
97 System.out.println("Using existing branch");
98 return sim.getSimulatedData().getBranch(0);
103 public void setName(String name){
107 public void setUnit(String unit){
111 public void setSymbol(String symbol){
112 this.symbol = symbol;
115 public void setExpression(String expression){
116 this.expression = expression;
117 builder = new ExpressionBuilder(expression);
120 // get a list of all the names of all the available variables
121 private ArrayList<String> getAllNames(){
122 ArrayList<String> names = new ArrayList<String>();
123 for (FlightDataType type : FlightDataType.ALL_TYPES)
124 names.add(type.getName());
125 for (CustomExpression exp : sim.getCustomExpressions() ){
127 names.add(exp.getName());
132 // get a list of all the symbols of the available variables ignoring this one
133 private ArrayList<String> getAllSymbols(){
134 ArrayList<String> symbols = new ArrayList<String>();
135 for (FlightDataType type : FlightDataType.ALL_TYPES)
136 symbols.add(type.getSymbol());
137 for (CustomExpression exp : sim.getCustomExpressions() ){
139 symbols.add(exp.getSymbol());
144 public boolean checkSymbol(){
145 if (symbol.trim().isEmpty())
149 for (char c : "0123456789.,()[]{}<> ".toCharArray())
150 if (symbol.indexOf(c) != -1 )
153 // No operators (ignoring brackets)
154 for (String s : CustomExpression.AVAILABLE_OPERATORS.keySet()){
155 if (symbol.contains(s.replaceAll("\\(|\\)", "")))
159 // No already defined symbols
160 ArrayList<String> symbols = getAllSymbols().clone();
161 if (symbols.contains(symbol.trim())){
162 int index = symbols.indexOf(symbol.trim());
163 log.user("Symbol "+symbol+" already exists, found "+symbols.get(index));
170 public boolean checkName(){
171 if (name.trim().isEmpty())
174 // No characters that could mess things up saving etc
175 for (char c : ",()[]{}<>".toCharArray())
176 if (name.indexOf(c) != -1 )
179 ArrayList<String> names = getAllNames().clone();
180 if (names.contains(name.trim())){
181 int index = names.indexOf(name.trim());
182 log.user("Symbol "+symbol+" already exists, found "+names.get(index));
189 // Currently no restrictions on unit
190 public boolean checkUnit(){
194 public boolean checkAll(){
195 return checkUnit() && checkSymbol() && checkName();
198 public String getName(){
202 public String getSymbol(){
206 public String getUnit(){
210 public String getExpressionString(){
216 * Check if the current expression is valid
218 public boolean checkExpression(){
220 if (expression.trim().isEmpty()){
224 // Define the available variables as 0
225 for (FlightDataType type : getBranch().getTypes()){
226 builder.withVariable(type.getSymbol(), 0.0);
229 for (String symb : getAllSymbols()){
230 builder.withVariable(symb, 0.0);
236 } catch (Exception e) {
237 log.user("Custom expression invalid : " + e.toString());
246 * Evaluate the expression using the last variable values from the simulation status.
247 * Returns NaN on any error.
249 public Double evaluate(SimulationStatus status){
251 for (FlightDataType type : status.getFlightData().getTypes()){
252 builder.withVariable(type.getSymbol(), status.getFlightData().getLast(type) );
257 calc = builder.build();
258 return new Double(calc.calculate());
259 } catch (Exception e) {
260 log.user("Could not calculate custom expression "+name);
266 * Returns the new flight data type corresponding to this calculated data
268 public FlightDataType getType(){
270 UnitGroup ug = new FixedUnitGroup(unit);
271 FlightDataType type = FlightDataType.getType(name, symbol, ug);
273 // If in a simulation, figure out priority from order in array so that customs expressions are always at the top
274 if (sim != null && sim.getCustomExpressions().contains(this)){
275 int totalExpressions = sim.getCustomExpressions().size();
276 int p = -1*(totalExpressions-sim.getCustomExpressions().indexOf(this));
284 * Add this expression to the simulation if not already added
286 public void addToSimulation(){
287 if (! sim.getCustomExpressions().contains(this))
288 sim.addCustomExpression( this );
292 * Removes this expression from the simulation, replacing it with a given new expression
294 public void overwrite(CustomExpression newExpression){
295 if (!sim.getCustomExpressions().contains(this))
298 int index = sim.getCustomExpressions().indexOf(this);
299 sim.getCustomExpressions().set(index, newExpression);
304 public String toString(){
305 return "Custom expression : "+this.name.toString()+ " " + this.expression.toString();
310 * Clone method makes a deep copy of everything except the simulation
311 * @see java.lang.Object#clone()
313 public Object clone() {
315 return super.clone();
317 catch( CloneNotSupportedException e )
319 return new CustomExpression( sim ,
320 new String(this.getName()),
321 new String(this.getSymbol()),
322 new String(this.getUnit()),
323 new String(this.getExpressionString()));