package org.cowboycoders.turbotrainers; import org.fluxoid.utils.TrapezoidIntegrator; public class PowerModel implements PowerModelManipulator { public static final double GRAVITATIONAL_ACCELERATION = 9.81; double windSpeed = 0;//2.94; double currentBearing = 340; double windDirectionDegrees = 160; // direction : 180 is south double airDensity = 1.2234; // kg·m−3 double incrementalDragAreaSpokes = 0.0044; double dragArea = 0.255;//57; //Coefficient of drag x frontal area double gradientAsPercentage = 0.00;// 0.3; double coefficentRollingResistance = 0.0032; double totalMass = 70.0; //double kineticEnergy = 0.; double momentOfInertiaWheels = 0.14; //kg m^2 for bike double outsideRadiusTire = 0.311; private boolean negativeVelocityAllowed = false; /* (non-Javadoc) * @see org.cowboycoders.turbotrainers.PowerModelManipulator#isNegativeVelocityAllowed() */ @Override public boolean isNegativeVelocityAllowed() { return negativeVelocityAllowed; } /* (non-Javadoc) * @see org.cowboycoders.turbotrainers.PowerModelManipulator#setNegativeVelocityAllowed(boolean) */ @Override public void setNegativeVelocityAllowed(boolean negativeVelocityAllowed) { this.negativeVelocityAllowed = negativeVelocityAllowed; } /* (non-Javadoc) * @see org.cowboycoders.turbotrainers.PowerModelManipulator#getWindSpeed() */ @Override public double getWindSpeed() { return windSpeed; } /* (non-Javadoc) * @see org.cowboycoders.turbotrainers.PowerModelManipulator#setWindSpeed(double) */ @Override public void setWindSpeed(double windSpeed) { this.windSpeed = windSpeed; } /* (non-Javadoc) * @see org.cowboycoders.turbotrainers.PowerModelManipulator#getCurrentBearing() */ @Override public double getCurrentBearing() { return currentBearing % 360; } /* (non-Javadoc) * @see org.cowboycoders.turbotrainers.PowerModelManipulator#setCurrentBearing(double) */ @Override public void setCurrentBearing(double currentBearing) { this.currentBearing = currentBearing; } /* (non-Javadoc) * @see org.cowboycoders.turbotrainers.PowerModelManipulator#getWindDirectionDegrees() */ @Override public double getWindDirectionDegrees() { return windDirectionDegrees % 360; } /* (non-Javadoc) * @see org.cowboycoders.turbotrainers.PowerModelManipulator#setWindDirectionDegrees(double) */ @Override public void setWindDirectionDegrees(double windDirectionDegrees) { this.windDirectionDegrees = windDirectionDegrees; } /* (non-Javadoc) * @see org.cowboycoders.turbotrainers.PowerModelManipulator#getAirDensity() */ @Override public double getAirDensity() { return airDensity; } /* (non-Javadoc) * @see org.cowboycoders.turbotrainers.PowerModelManipulator#setAirDensity(double) */ @Override public void setAirDensity(double airDensity) { this.airDensity = airDensity; } /* (non-Javadoc) * @see org.cowboycoders.turbotrainers.PowerModelManipulator#getIncrementalDragAreaSpokes() */ @Override public double getIncrementalDragAreaSpokes() { return incrementalDragAreaSpokes; } /* (non-Javadoc) * @see org.cowboycoders.turbotrainers.PowerModelManipulator#setIncrementalDragAreaSpokes(double) */ @Override public void setIncrementalDragAreaSpokes(double incrementalDragAreaSpokes) { this.incrementalDragAreaSpokes = incrementalDragAreaSpokes; } /* (non-Javadoc) * @see org.cowboycoders.turbotrainers.PowerModelManipulator#getDragArea() */ @Override public double getDragArea() { return dragArea; } /* (non-Javadoc) * @see org.cowboycoders.turbotrainers.PowerModelManipulator#setDragArea(double) */ @Override public void setDragArea(double dragArea) { this.dragArea = dragArea; } /* (non-Javadoc) * @see org.cowboycoders.turbotrainers.PowerModelManipulator#getGradientAsPercentage() */ @Override public double getGradientAsPercentage() { return gradientAsPercentage; } /* (non-Javadoc) * @see org.cowboycoders.turbotrainers.PowerModelManipulator#setGradientAsPercentage(double) */ @Override public void setGradientAsPercentage(double gradientAsPercentage) { this.gradientAsPercentage = gradientAsPercentage; } /* (non-Javadoc) * @see org.cowboycoders.turbotrainers.PowerModelManipulator#getCoefficentRollingResistance() */ @Override public double getCoefficentRollingResistance() { return coefficentRollingResistance; } /* (non-Javadoc) * @see org.cowboycoders.turbotrainers.PowerModelManipulator#setCoefficentRollingResistance * (double) */ @Override public void setCoefficentRollingResistance(double coefficentRollingResistance) { this.coefficentRollingResistance = coefficentRollingResistance; } /* (non-Javadoc) * @see org.cowboycoders.turbotrainers.PowerModelManipulator#getTotalMass() */ @Override public double getTotalMass() { return totalMass; } /* (non-Javadoc) * @see org.cowboycoders.turbotrainers.PowerModelManipulator#setTotalMass(double) */ @Override public void setTotalMass(double totalMass) { this.totalMass = totalMass; } /* (non-Javadoc) * @see org.cowboycoders.turbotrainers.PowerModelManipulator#getKineticEnergy() */ @Override public double getKineticEnergy() { synchronized (this) { return kineticEnergy.getIntegral(); } } private void setKineticEnergy(double newEnergy) { synchronized (this) { kineticEnergy.setIntegral(newEnergy); } } /* (non-Javadoc) * @see org.cowboycoders.turbotrainers.PowerModelManipulator#getMomentOfInertiaWheels() */ @Override public double getMomentOfInertiaWheels() { return momentOfInertiaWheels; } /* (non-Javadoc) * @see org.cowboycoders.turbotrainers.PowerModelManipulator#setMomentOfInertiaWheels(double) */ @Override public void setMomentOfInertiaWheels(double momentOfInertiaWheels) { this.momentOfInertiaWheels = momentOfInertiaWheels; } /* (non-Javadoc) * @see org.cowboycoders.turbotrainers.PowerModelManipulator#getOutsideRadiusTire() */ @Override public double getOutsideRadiusTire() { return outsideRadiusTire; } /* (non-Javadoc) * @see org.cowboycoders.turbotrainers.PowerModelManipulator#setOutsideRadiusTire(double) */ @Override public void setOutsideRadiusTire(double outsideRadiusTire) { this.outsideRadiusTire = outsideRadiusTire; } public double getTangentialWindVelocity() { double windDirectionRadians = Math.toRadians(getWindDirectionDegrees()); double bearingRadians = Math.toRadians(this.getCurrentBearing()); return Math.cos(bearingRadians - windDirectionRadians) * getWindSpeed(); } public double getNormalWindVelocity() { double windDirectionRadians = Math.toRadians(getWindDirectionDegrees()); double bearingRadians = Math.toRadians(this.getCurrentBearing()); return Math.sin(bearingRadians - windDirectionRadians) * getWindSpeed(); } /* (non-Javadoc) * @see org.cowboycoders.turbotrainers.PowerModelManipulator#getVelocity() */ @Override public double getVelocity() { double energy = getKineticEnergy(); double I = getMomentOfInertiaWheels(); double r = this.getOutsideRadiusTire(); boolean negative = energy < 0 ? true : false; energy = Math.abs(energy); double velocity = Math.sqrt(2 * energy / (getTotalMass() + I / Math.pow(r, 2))); if (negative) return -velocity; return velocity; //return 8.36; } /** * Force power model to a new velocity * * @param velocity the new velocity */ @Override public synchronized void setVelocity(final double velocity) { double I = getMomentOfInertiaWheels(); double r = this.getOutsideRadiusTire(); // energy required for given speed double energy = 0.5 * Math.pow(velocity, 2) * (getTotalMass() + I / Math.pow(r, 2)); // negative energy for negative velocities if (velocity < 0) { energy = -energy; } setKineticEnergy(energy); } /* (non-Javadoc) * @see org.cowboycoders.turbotrainers.PowerModelManipulator#getAirVelocity() */ @Override public double getAirVelocity() { return getTangentialWindVelocity() + getVelocity(); } /* (non-Javadoc) * @see org.cowboycoders.turbotrainers.PowerModelManipulator#getYaw() */ @Override public double getYaw() { double radians = 0.; double airVelocity_ = getAirVelocity(); if (airVelocity_ == 0) { radians = Math.PI / 2; } else { radians = Math.atan(getNormalWindVelocity() / getAirVelocity()); } return radians; } public double getPowerLostToAerodynamicDrag() { double Vg = Math.abs(getVelocity()); double Va = getAirVelocity(); double p = getAirDensity(); double CdA = getDragArea(); double Fw = this.getIncrementalDragAreaSpokes(); return 0.5 * Math.pow(Va, 2) * Vg * p * (CdA + Fw); } /** * Gradient transformed to angle */ public double getRoadAngle() { return Math.atan(this.getGradientAsPercentage() / 100); } public double getPowerLostToRollingResistance() { double Vg = Math.abs(getVelocity()); double roadAngle = getRoadAngle(); double Crr = getCoefficentRollingResistance(); double mass = getTotalMass(); double g = GRAVITATIONAL_ACCELERATION; return Vg * Math.cos(roadAngle) * Crr * mass * g; } public double getPowerLostToWheelBearings() { double Vg = Math.abs(getVelocity()); return Vg * (91 + 8.7 * Vg) * Math.pow(10, -3); } public double getPowerToIncreasePotentialEnergy() { double mass = getTotalMass(); double Vg = getVelocity(); double g = GRAVITATIONAL_ACCELERATION; double roadAngle = getRoadAngle(); return Vg * mass * g * Math.sin(roadAngle); } private TrapezoidIntegrator kineticEnergy = new TrapezoidIntegrator(); // private boolean running = false; // // private synchronized void start() { // if (running) return; // updatePower(0); // running = true; // } public double updatePower(double power) { double kineticPower; // if (power < 0 && getVelocity() < 0.00001 && !isNegativeVelocityAllowed() ) { // kineticPower = // 0; // } if (getVelocity() >= 0) { kineticPower = power - getPowerLostToAerodynamicDrag() - getPowerLostToRollingResistance() - getPowerToIncreasePotentialEnergy() - getPowerLostToWheelBearings(); } else { kineticPower = power + getPowerLostToAerodynamicDrag() + getPowerLostToRollingResistance() + getPowerToIncreasePotentialEnergy() + getPowerLostToWheelBearings(); } double timestamp = getTimestamp(); synchronized (this) { kineticEnergy.add(timestamp, kineticPower); } double velocity = getVelocity(); if (!isNegativeVelocityAllowed() && velocity < 0) { velocity = 0.; reset(); } return velocity; } public void reset() { synchronized (this) { kineticEnergy = new TrapezoidIntegrator(); } } protected double getTimestamp() { return System.nanoTime() / Math.pow(10, 9); } public static void main(String[] args) throws InterruptedException { PowerModel pm = new PowerModel() { double timestamp = System.nanoTime() / Math.pow(10, 9); protected double getTimestamp() { return timestamp += 1; } }; pm.setVelocity(20.0); double ACCURACY = 0.00000000001; assert pm.getVelocity() < 20 + ACCURACY && pm.getVelocity() > 20 - ACCURACY : "setVelocity is bust"; System.out.println("initial velocity = " + pm.getVelocity()); pm.setNegativeVelocityAllowed(false); for (int i = 0; i < 1000; i++) { System.out.println(pm.updatePower(400)); } for (int i = 0; i < 10000; i++) { System.out.println(pm.updatePower(0)); } } }