/*
* Licensed to the Ted Dunning under one or more contributor license
* agreements. See the NOTICE file that may be
* distributed with this work for additional information
* regarding copyright ownership. Ted Dunning licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.mapr.synth.drive;
import java.io.Serializable;
/**
* Simulates engine behavior with automatic transmission.
* <p>
* The basic idea is that we have a transmission with different gears.
* The transmission will shift to a higher gear as RPM's reach a high
* shift point and will shift down as RPM's reach a low shift point.
* <p>
* The shift points depend a bit on load which, in turn, depends on
* whether we are accelerating or decelerating.
* <p>
* The input is a desired speed. The throttle setting will be selected
* based on whether we are too slow or fast. That will determine the load
* which in turn determines the shift points. The engine load and transmission
* setting will determine how the speed changes and off we go.
* <p>
* So far, this works pretty well, but it doesn't the engine braking emulation
* is kind of a hack.
*/
public class Engine implements Serializable {
private static final double THROTTLE_CONTROL_GAIN = 50;
private static final double MAX_THROTTLE = 100;
private static final double ACCELERATION_BACKOFF = 30;
// observed transmission properties for typical turbo-diesel
private static final double[] MPS_BY_RPM = {
4.4704 / 2000, 8.9408 / 2000, 13.4112 / 2000, 13.4112 / 1500, 17.8816 / 1500, 22.3520 / 1500, 22.3520 / 1000
};
private static final int TOP_GEAR = MPS_BY_RPM.length - 1;
private static final double ZERO_TORQUE_RPM = 3500;
private static final double LOW_SHIFT = 1000;
private static final double HIGH_SHIFT = 2000;
// The throttle has some turbo delay.
public static final double THROTTLE_TIME_CONSTANT = 0.8;
// in kg, not a super light car
private static final double VEHICLE_MASS = 2000;
// in watts (this is about 200 HP)
private static final double MAX_POWER = 150e3;
private static final double TORQUE_AT_ZERO = (4 * MAX_POWER / ZERO_TORQUE_RPM);
// assuming 150 MPH absolute max speed
private static final double DRAG_COEFFICIENT = 0.4875334;
private static final double SHIFT_TIME = 0.1;
private static final double BRAKING_GAIN = 1;
// this determines the time resolution of our computation (in s)
private double dt = 0.01;
private double currentTime = 0;
// throttle is a slow threshold function that
// rises asymptotically to a maximum as long as we are too slow
// and falls fairly more quickly when we are too fast
private double currentThrottle = 0;
private double brakeForce = 0;
private double shiftTimeOut = 0;
private int currentGear = 0;
private double currentSpeed = 0;
private double currentRPM = 0;
private double currentAcceleration;
private double currentDistance = 0;
public Engine() {
}
public Engine(Engine eng) {
this();
brakeForce = eng.brakeForce;
currentAcceleration = eng.currentAcceleration;
currentDistance = eng.currentDistance;
currentGear = eng.currentGear;
currentRPM = eng.currentRPM;
currentSpeed = eng.currentSpeed;
currentThrottle = eng.currentThrottle;
currentTime = eng.currentTime;
dt = eng.dt;
}
/**
* Runs the simulation up to just past the desired sampleTime with a specified
* target speed.
*
* @param sampleTime When to stop the simulation and return
* @param speedTarget The speed we would like to reach
* @param maxBrake The maximum amount of braking in g's. Typically 0.1 for gentle driving, 1 for maniacs.
*/
public void stepToTime(double sampleTime, double speedTarget, double maxBrake) {
while (currentTime < sampleTime) {
// throttle rises or falls quite fast unless we are close to the desired speed
// when speed is close, we switch to a bit of a proportional control
double desiredThrottle = THROTTLE_CONTROL_GAIN * (speedTarget - currentSpeed) - ACCELERATION_BACKOFF * currentAcceleration;
desiredThrottle = Math.min(MAX_THROTTLE, desiredThrottle);
desiredThrottle = Math.max(0, desiredThrottle);
currentThrottle += (desiredThrottle - currentThrottle) / THROTTLE_TIME_CONSTANT * dt;
// gear box with a bit of slip. We need the slip to get non-zero power when speed == 0
currentRPM = currentSpeed / MPS_BY_RPM[currentGear] + 200;
// shifting algorithm is simplistic, but good enough
if (currentRPM > HIGH_SHIFT && currentGear < TOP_GEAR) {
currentGear++;
shiftTimeOut = currentTime + SHIFT_TIME;
} else if (currentRPM < LOW_SHIFT && currentGear > 0) {
currentGear--;
shiftTimeOut = currentTime + SHIFT_TIME;
}
// a shift might have changed our RPM's.
currentRPM = currentSpeed / MPS_BY_RPM[currentGear] + 200;
// if we are moving along, then we allow for 5% of the throttle setting to emulate engine braking
// the constant negative value represents engine losses
double powerSetting = currentThrottle / 100.0 - 0.05;
if (shiftTimeOut > currentTime) {
powerSetting = 0;
}
// power is a combination of assumed linear decrease in torque from zero to some high RPM
double engineForce = TORQUE_AT_ZERO / MPS_BY_RPM[currentGear] * (1.0 - currentRPM / ZERO_TORQUE_RPM) * powerSetting;
// drag is based on an asymptotic max speed of 150 MPH where aerodynamic drag == enginePower
// the extra drag is engine drag
double dragForce = DRAG_COEFFICIENT * currentSpeed * currentSpeed;
if (maxBrake > 0 && currentThrottle < 2 && speedTarget < currentSpeed) {
brakeForce += VEHICLE_MASS * BRAKING_GAIN * (currentSpeed - speedTarget) * dt;
brakeForce = Math.min(brakeForce, VEHICLE_MASS * maxBrake * Constants.G);
} else {
brakeForce = 0;
}
// force applied to change speed is a simple net, but with a hack to avoid infinite torque at zero speed
double netForce = engineForce - dragForce - brakeForce;
// note that we don't allow crazy acceleration ... the tires would break loose
currentAcceleration = Math.min(8, netForce / VEHICLE_MASS);
double oldSpeed = currentSpeed;
currentSpeed += currentAcceleration * dt;
currentSpeed = Math.max(0, currentSpeed);
currentDistance += (oldSpeed + currentSpeed) * dt / 2;
currentTime += dt;
}
}
public double getSpeed() {
return currentSpeed;
}
public double getThrottle() {
return currentThrottle;
}
public double getRpm() {
return currentRPM;
}
public int getGear() {
return currentGear;
}
public void setDistance(double distance) {
this.currentDistance = distance;
}
public double getDistance() {
return currentDistance;
}
public void setTime(double time) {
this.currentTime = time;
}
}