Thursday, June 30, 2016

SparkEV performance analysis

You get excited about a car's performance, and the most bold statement a car can make is acceleration, especially 0 to 60 MPH time. Indeed, we get excited about Tesla P90DL time of 2.9 seconds and even Veryon's 2.4 seconds, yet we hardly notice cars like Mitsubishi Mirage for getting 44 MPG as a gas engine car (not hybrid) with 0-60 MPH time close to 14 seconds.

That's what makes SparkEV so exciting: it is the quickest car under $20K in 0-60 MPH at 7.2 seconds while achieving 119 MPGe (EPA) or equivalent to a gas car that gets over 64 MPGe$ when gas is $2.5/gal and electricity at $0.17/kWh (efficiency at 4.4 mi/kWh wall to battery). There is no car that achieves this level of performance and efficiency for such low price.

But does SparkEV really get 0-60 MPH time of 7.2 seconds? In my informal testing, time is always less than 7 seconds, though I'm probably biased. Some claim that the time is exaggerated, and the actual time is closer to 8 seconds, but it's hard to know if they used correct conditions. To test the peak performance of any EV, the battery must be new, fully charged and cool, tires warm enough to hold traction, and many other factors. It might seem silly to talk about traction on 7.2 sec car, but if you drive SparkEV, you know that the traction control regularly kicks in when you stomp on the accelerator, especially on even slightly bumpy road. There is also the question of driver's reaction time as well as roll out uncertainty.

In this blog post, I'll do what I did with "can stock Corvette beat Tesla P90DL in 0-60 MPH" and "range polynomial" blog posts : make a model of car's acceleration from power/torque curves. Then the acceleration curve can be applied without regard to such things as driver reaction time. As before, one should heed this giant caveat.


I will give explanations on how these plots are obtained as well as the source code. One is free to examine them and decide how valid these plots may be. Since the numbers come out roughly what's found through various other experiments, these are probably close to reality.

Experimental data

There are many experimental data on 2014 SparkEV, but none available for 2015/2016 other than Chevy's web site. While all years of SparkEV have the same power and rear wheel torque, 2014 had 21 kWh of A123 battery and it's heavier by about 100 lb compared to later years. As such, 2015/2016 would have better performance characteristics. Because we are making a model of performance, we have to start with 2014 and check/tweak our model until it matches the experimental data. Once that's done, same process can be applied to later years.

We have the following experimental data for 2014 SparkEV.

Chevy web site (no longer available)
0-60 MPH: 7.5 seconds
0-60 MPH: 7.5 seconds
1/4 mile: 16 sec at 87.6 MPH.
0-30 MPH: 3.2 seconds (car and driver)
0-60 MPH: 7.9 seconds (car and driver)
0-60 MPH: 7.5 (best run)
1/4 mile: 15.8 seconds @ 86.12 mph
0-30 MPH: 3.1 sec
0-60 MPH: 8 sec

In summary, following can be expected.
0-30 MPH: bit over 3 seconds
0-60 MPH: bit over 7.5 seconds
1/4 mile: under 16 seconds at about 86 MPH

2014 SparkEV

Since I don't know the test conditions, I plot several scenarios:

1. 2014 with drag and 150 lb driver
2. 2014 with drag but no driver
3. 2014 with drag and 75 lb driver (dog driving?)
4. 2014 without drag and 150 lb driver

speed (mph)2014 time (sec)2014 gforce2014 no driver time (sec)2014 no driver gforce2014 75lb driver time (sec)2014 75lb driver gforce2014 no drag time (sec)2014 no drag gforce

The times range from 7.29 seconds without driver (#2) to 7.67 seconds with 150 lb driver (#1). Assuming 75 lb driver is 7.48 seconds, closer to Chevy's claim. But I think it's against the law in most places to have your dog drive the car!

Kidding aside, the model is roughly in line with experimental data, though realistic case of driver + drag is about 0.17 seconds slower. Indeed, Mark from insideevs measured 3.1 seconds to 30 MPH using SparkEV with 1.5 year old and 16K miles worn battery, and the model shows 0.26 seconds slower at 3.36 seconds to 30 MPH. As you read forward, keep in mind that actual could be 0.17 to 0.26 seconds quicker than the graphs / tables shown in this blog post.

Note the g-force that's slightly above 0.4g until about 35 MPH and the gradual decrease. While this is pitiful compared to motorcycles and Tesla P90DL, contrast that to Nissan Leaf that pull maximum g only up to about 25 MPH then takes 7 seconds to accelerate from 30 MPH to 60 MPH, and you can see why SparkEV is such an acceleration beast.

I think this validates the model methodology as "close enough". Let's move on to 2015.

2015 SparkEV

2015 is about 100 lb lighter than 2014 while having the same horsepower rating. Motor torque is less, but that is compensated with lower gearing to have the same torque at the wheels, and one would expect quicker times. Since one would typically carry "stuff" and make it heavier, I plot from 0 lb to 700 lb added weights in 100 lb increments (always assume 150 lb for driver as a given). 700 lb extra (which is 850 lb including the driver) is roughly the gross vehicle weight rating (GVWR) of the car, the maximum rated weight.

speed (mph)3016lb time (sec)3016lb gforce3116lb time (sec)3116lb gforce3216lb time (sec)3216lb gforce3316lb time (sec)3316lb gforce3416lb time (sec)3416lb gforce3516lb time (sec)3516lb gforce3616lb time (sec)3616lb gforce3716lb time (sec)3716lb gforce

Chevy advertises 7.2 seconds in 0-60 MPH, but no one seem to have actual drag strip data for 2015, so we don't have any basis for comparison other than Chevy's claim. The data shows that 0 lb added (but with 150 lb rider) is 7.36 seconds. Going by 0.17 second discrepancy, I suspect it'd be closer to 7.2 seconds in actual test. Then the range of acceleration through all possible additional weight is between 7.2 seconds and 9.2 seconds.

An interesting observation here is that SparkEV fully loaded with 700 lb (850 lb with driver) at 9.2 seconds would be quicker than Leaf and eGolf without any addtional load to 60 MPH (both around 10 seconds). In fact, fully loaded SparkEV is probably quicker than even unladen Toyota Mirai fuel cell car that cost $60K. That's very impressive for a car that costs $16K in CA ($18K outside of CA).

Next, the question is, what happens when you start to shed weight? There was a guy who removed body parts from Nissan Leaf to make it lighter by about 1000 lb. End result was weight to power ratio of about SparkEV.

Then let's see what happens when SparkEV starts shedding weight. Certainly, things like passenger seats and rear hatch / doors would be hundreds of pounds. I plot from 0 lb to 1000 lb removed in 100 lb increments.

speed (mph)2016lb time (sec)2016lb gforce2116lb time (sec)2116lb gforce2216lb time (sec)2216lb gforce2316lb time (sec)2316lb gforce2416lb time (sec)2416lb gforce2516lb time (sec)2516lb gforce2616lb time (sec)2616lb gforce2716lb time (sec)2716lb gforce2816lb time (sec)2816lb gforce2916lb time (sec)2916lb gforce

With 1000 lb lighter, SparkEV would do 0-60 MPH in under 5 seconds, quicker than Tesla S70! But 1000 lb lighter is not realistic since that probably wouldn't be street legal. More realistic would be 200 lb lighter at most (ie. train your dog to drive!). That would result in 6.86 seconds. Chevy stated that Bolt will be under 7 seconds, so could SparkEV on mild diet be quicker than Bolt? At least for 0-30 MPH of 3 seconds and taking 0.17/0.26 second discrepancy into account, SparkEV would be quicker than Bolt's 2.9 seconds. We'll explore Bolt later in this post.

Climbing acceleration

What happens when accelerating on hills? Obviously, it will be slower, but how much slower will it be? I plot 2015 SparkEV with 150 lb rider over various percent grades of hills from 0% (flat road) to 20%.

2015 SparkEV acceleration over speed (mph)0% grade time (sec)0% grade gforce1% grade time (sec)1% grade gforce2% grade time (sec)2% grade gforce3% grade time (sec)3% grade gforce5% grade time (sec)5% grade gforce8% grade time (sec)8% grade gforce13% grade time (sec)13% grade gforce21% grade time (sec)21% grade gforce

Few interesting observations can be made.

1. Leaf and eGolf have 0-60 MPH time of about 10 seconds. That's like SparkEV accelerating on 8% grade. 8% grade is steeper than most roads, like "grape vine" (aka, Tejon pass) in SoCal.

2. 0-60 MPH time for iMiev is about 13 seconds and tiny 2 seater like SmartED has 11.5 seconds. That's like SparkEV accelerating on 13% to 14% grade. That is steeper than many driveways.

3. Chevy's disgraceful Iron Duke Camaro supposedly did 0-60 MPH in 20 seconds. That's about SparkEV on 20% grade. 20% grade is incredibly steep when you see it in person, about 1000 ft rise for every mile driven, almost like airplane take off.

One should keep in mind that these are only 0-60 MPH times. Those cars may be tuned for lower speed acceleration, and could be quicker than SparkEV on hill at different speeds (ie, Leaf to 30 MPH). Still, SparkEV accelerating up a hill could be kicking butt of many cars on flat road.

Climbing ability

In previous blog post "SparkEV range polynomial climbing hill", I explored SparkEV's climbing ability.

While maximum climbing was left as homework for the reader, it's not possible to know the maximum without knowing the torque at the wheels, which this blog post is based on. Below plot shows maximum climbing ability over various weights. Again, lowest weight includes 150 lb driver on 2015 SparkEV and heaviest is about GVWR case.

speed (mph)3016lb climb (%)3116lb climb (%)3216lb climb (%)3316lb climb (%)3416lb climb (%)3516lb climb (%)3616lb climb (%)3716lb climb (%)

Chevy claims maximum grade at start is only about 25%. But if we go by the torque available at the wheels, it's closer to 45%! Why the discrepancy? Maybe the computer limits the torque at low speed? Regardless, I just set it to 25% at 0 MPH in the analysis.

Note that climbing ability is less than 7% grade at 90 MPH, the maximum speed of the car limited by electronics. But even if it's not limited by electronics, maximum speed may not be much more. Torque graph drops almost linearly while the force to overcome drag would increase proportional to the square of speed (power is cube of speed). I suspect SparkEV won't go much faster than 95 MPH, if that, even without electronic speed limit.

Theoretical top speed

Often the forum discussion turns to top speed of SparkEV. It's electronically limited to 90 MPH, but if there is no such restriction, what would be the top speed? Simply going by gears, and assuming the motor can spin to 10,000 RPM like other EV motors, it yields top speed close to 200 MPH! Obviously, that won't happen due to drag forces. Since we have the driving force, we can extend (ie, make up numbers, my favorite activity) to see at what speed the driving force would not be enough to overcome the drag force.

What I do is to take the last few seemingly linear samples of torque data (blue plot) and interpolate a straight line from there (green plot). Then I plot the drag data (red plot) and see where they intersect. That occurs at about 96 MPH. But even if there's no drag, the motor would top out at about 103 MPH. Were they shooting for 105 MPH to match it to 105 kW the motor is capable of? Maybe this is a little Easter egg for us! Or not; remember, these are all made up numbers.

Bolt guess

From Chevy's web site, Bolt is expected to be 2.9 seconds for 0-30 MPH. Chevy claims under 7 seconds for 0-60 MPH, but they don't say how much less. Something we can guess is Bolt's performance if it has torque profile of SparkEV. We simply scale SparkEV's torque curve to Bolt's peak torque and run the analysis. Yes, it's a total guess, but what the heck, let's see what happens.

speed (mph)bolt time (sec)bolt gforcebolt no driver time (sec)bolt no driver gforcebolt 75lb driver time (sec)bolt 75lb driver gforcebolt no drag time (sec)bolt no drag gforce

Model shows 0-30 MPH time of 2.98 seconds, pretty close to Bolt's advertised time. Does this mean model's 0-60 MPH time of 6.75 seconds will be about 6.7 seconds? Maybe, maybe not. I'll be very surprised if it turns out that way. That might be the day I buy some lottery tickets.

Tangent thoughts: more gears

This blog post was re-written countless times. I started with simple analysis, which turned into dozens of plots, and into tangents like trying to find the gear ratio that will maximize 0-60 MPH time. Of course, gear change is not possible, so much of that was meaningless; they were just out of curiosity. While I won't clutter this blog post with all other tangents, I will add peak acceleration gear ratios. Who knows? Some lunatic may replace the gearbox to have quicker 0-60 MPH time, or maybe even have 3 speed massless gearbox.

We know the stock acceleration is based on top speed of 90 MPH. Then to achieve quicker acceleration, one must reduce the peak speed. Above plots' abscissa shows peak speed with gearing from 90 MPH. Essentially, 31 MPH is bit over 1/3 ratio, 62 MPH is bit over 2/3. With those gear changes, 0-30 MPH can be about 1.56 seconds, 0-60 MPH can be about 6.57 seconds.

Those are some quick times, rivaling BMW i3 and maybe even Bolt, but the top speed would be impractical. It's too bad SparkEV doesn't have 3 speed transmission. But then again, the tires squeal even with current level of acceleration, and even higher acceleration may not achieve much quicker times.

Something one can try is to see what would be the absolute maximum speed with changing the gear ratio. Recall above plot that SparkEV can barely make 103 MPH with current gearing. With gear change to move the torque curve to higher speed and sacrificing acceleration, what gear ratio would allow the highest possible speed? Homework for the reader!

Edit: 2016-10-20

While browsing youtube, I came across an excellent episode by Engineering Explained about front wheel drive cars. He uses an example of 50-50 weight distribution car with low center of mass and short wheel base to analyze the maximum acceleration of a car that has tires with coefficient of friction of 1. That seems to describe SparkEV, except for the tires' coefficient of friction.

In the episode, the conclusion is that such car would have peak acceleration of about 0.4g. And what do you know? That's about the peak acceleration of SparkEV! And as anyone who drive SparkEV could tell you, stomping on the throttle regularly kicks in the traction control. So it seems SparkEV power is tuned for what is possible for "typical" tires, and no more. Unless torque can be increased above 30 MPH, times shown in this blog post are close to the maximum one could get with SparkEV, even with different gearing.

Comparisons to other cars

Now that the model data validates experimental data to some degree, let's compare to some other cars. Insideevs has nice compilation of various EV performance.

Model0-300-6030-60Source 30-60
Tesla Model S P851.74.22.5Insideevs(1) (Tesla Motors)
Tesla Model S 85 base2.75.62.9Insideevs(2) (Consumer reports)
BMW i3 (BEV version) Edmunds
V6 Camaro 323 HP2.66.64Insideevs(2) (Consumer reports)
SparkEV 20143.47.64.2model
SparkEV 20153.27.44.2model
BMW i3 Rex3.37.54.2Insideevs(2) (Consumer reports)
SparkEV 20144.7Insideevs(1)
SparkEV 20143.184.9Insideevs(3)
Honda Fit5.4Insideevs(1)
2011 Volt5.8Insideevs(1)
Fiat 500e5.9Insideevs(1)
2016 VoltInsideevs(4)

Insideevs(2)=comments in
Insideevs(4)=2016 Volt stat

SparkEV is 1.4 seconds quicker to 60 MPH than the next on the list, Honda FitEV that's been discontinued for years. SparkEV even rivals BMW i3 REx that cost double and incorporate carbon fiber in its body. SparkEV even comes close to 323 HP V6 Camaro in 30-60 MPH acceleration. This is from a $16K subcompact city car. That is just WOW!

Conclusion of results

Using a model based on torque curve, I showed that 2014 experimental data roughly matches the model data. Using the model, I showed that Chevy's claim of 0-60 MPH in 7.2 second for 2015 is plausible, though bit optimistic. I explored hill climbing ability and showed that 90 MPH is close to the maximum speed of the car even without electronic speed limiting.

How they're made

Once again, we go to the sausage factory to see the meat grinder doing the work. As before, Octave is used.

Following parameters are needed:

1. Lots of free time to read sparsely commented  code
2. Freeware GNU Octave (Matlab may not work due to plotyy function)
3. Experimental data from various sources
4. Motor torque characteristics plot
5. Drag force for various speeds and weight
6. Other data such as gear ratio, tire diameter, etc.

Motor torque characteristics plot

The essense of the model is torque available at the wheels at various speeds. Combined with the tire diameter, we can find the force that's pushing the car. Such data is available in graph form.

I don't know where this plot came from, but from the looks of it, it probably came from Chevy. Are we to trust it? Well, if the model fits experimental data from other sources, it's probably good. Combined with drag data from ecomodder web site, we can make a model of the car's performance.

First thing to do is to extract numbers from torque curve graph. One can do this manually by relating pixel locations to axis. Fortunately, there are tools that allow you to do this without manually finding pixels. An excellent tool is called "WebPlotDigitizer" that works in web browser.

Then we get these data points.

The data from the graph is not quite correct since the graph could be based on top of the "dot", middle, bottom, or anywhere in between. What we do is to correct the torque values based on known data. We know the peak torque on 2014 is 400 ft-lb at the motor with 3.17 reduction gear ratio. 2016 data is obtained from Chevy web site.

Torque is 327 ft-lb at the motor with 3.87 reduction gear ratio. Doing the math shows 1268 ft-lb for 2014 vs 1266 ft-lb for 2016 (note: above plot is N-m, not ft-lb), close enough to say that rear wheel torque are the same. Power is rated to be 105 kW (140 HP), but I recall some older publications showed 103 kW (137 HP). Let's stick to 105 kW.

Torque data from the graph is first scaled to match this peak torque number, and power at the wheels is computed. Then we "stretch" the speed corresponding to torque until this peak power is achieved. Resulting data would have 400 ft-lb peak with 3.17 gearing and 105 kW (140 HP) peak.

Below are plots with just torque scaling  (uncorrected) and power correction (corrected). Peak power occurs at 45 MPH. Assuming power out of the battery is 120 kW, it would be almost 88% efficient. Where did I get 120 kW? That's the maximum number I saw on display while hard accelerating up a hill.

A word of caution on efficiency. It is only with regard to fully stepping down on the accelerator, a worst case scenario. If the accelerator is only partially depressed, it is unknown what the efficiency would be. It may not scale linearly and most likely, it would be much higher, though the exact number is impossible to know without experiments. Still, be gentle with the accelerator if you want to conserve energy.

Something that's not clear is if the torque values are from braking horsepower (BHP) or if it's taken while accelerating. The distinction is important, because acceleration would result in angular acceleration of rotating parts (motor, gears, wheels) eating into the torque budget. Since the model seems to match the experimental data, we assume the torque figure takes into account the angular acceleration of rotating parts.

Then the question is how quick was the angular acceleration when measured? Using the most powerful engineering tool in the universe as described in the The hitchhiker's guide to the galaxy, SEP (someone else's problem)!

Dyno for everyone!

A side thought: a poor man's dyno can be made by simply measuring the angular acceleration of the tires at full throttle while the car is on jacks. One can use cheap optical sensors with strips of aluminum tape from dollar store taped to tires and microcontroller to measure the angular acceleration as the throttle is fully stomped on. Due to differential gear, it may need some tweaks, but it could be a way to measure dynamic torque / horsepower of the car very cheaply, albeit dangerously. Moment of inertia of the wheels/tires is easy to measure with standard apple (the fruit, not the computer/phone) and some strings. It would only take seconds to measure the torque / power. Ok, now back to SparkEV.

Drag force for various speeds and weight

Drag forces for various speeds are found by using ecomodder web site. The weight and aerodynamic parameters are from "reputable sources" (haha!), and drag only depend on them as opposed to power that also depend on motor efficiency. The drag values should be pretty close to actual.

Driver weight is assumed to be 150 lb. Below is the case for 2014 model year.^2&FuelWh=33557&IceEfficiency=.9&DrivetrainEfficiency=.95&ParasiticOverhead=500&rho=1.225&FromToStep=5-200-5

Below is to get drag data for 2015 model year by simply changing the weight.^2&FuelWh=33557&IceEfficiency=.9&DrivetrainEfficiency=.95&ParasiticOverhead=500&rho=1.225&FromToStep=5-200-5

Below is to get drag data for GVWR (3761 lb)^2&FuelWh=33557&IceEfficiency=.9&DrivetrainEfficiency=.95&ParasiticOverhead=500&rho=1.225&FromToStep=5-200-5

One thing to note is that Webplotdigitizer spits out 80 data points while ecomodder spits out 19 data points. To reconcile, I again use polynomial on drag data. Since drag force data is determined by running a math model at ecomodder web site, I'm basically reversing that process by using second order polynomial (recall power is third order, then force is second order). Then I plug in 80 speed parameters to the polynomial, and it will be accurate to any speed. In essence, it's interpolation with 100% accuracy at any speed! (yeah, sure).

Other data

Tire diameter for 185/55R15 tires is found from as 23 inches.

All the others can be found in Chevy web site or various google searches.


I can go through the code and explain each line, but this blog post is running way too long as is. Besides, I think the code is entirely self explanatory, no further comment needed (the famous last words before software engineer getting fired)! So without further delay, below is the m-file for you to try out in Octave.

close all; clear;

global fname; fname = 'acceleration.csv';
dlmwrite(fname, '');

% parameters from torque graph
speed_mph =[0.00 0.89 2.17 3.35 4.53 5.71 6.89 8.07 9.25 10.43 11.61 ...
            12.79 13.96 15.14 16.32 17.50 18.68 19.86 21.04 22.22 ...
            23.40 24.58 25.76 26.94 28.12 29.29 30.47 31.65 32.83 ...
            34.01 35.19 36.37 37.55 38.73 39.94 41.09 42.27 43.45 ...
            44.30 45.28 46.32 47.30 48.27 49.23 50.25 51.38 52.56 ...
            53.74 54.92 56.09 57.27 58.45 59.63 60.81 61.99 63.17 ...
            64.35 65.53 66.71 67.89 69.07 70.25 71.42 72.60 73.78 ...
            74.96 76.14 77.32 78.50 79.68 80.86 82.04 83.22 84.40 ...
            85.58 86.54 87.49 88.38 89.36 90];
torque_nm =[1630 1631 1718 1724 1717 1715 1715 1715 1715 1715 1715 ...
            1715 1715 1715 1715 1715 1715 1715 1715 1715 1715 1715 ...
            1715 1717 1722 1723 1719 1710 1701 1691 1681 1668 1654 ...
            1639 1615 1595 1565 1530 1494 1457 1418 1378 1340 1303 ...
            1265 1234 1199 1166 1134 1104 1075 1047 1021 995.4 970.5 ...
            946.5 924.3 902.4 882.3 862.9 844.6 826.8 809.8 792.9 ...
            777.3 764.2 752.6 741.9 731.3 720.1 706.7 690.4 669.3 ...
            640.0 602.8 558.9 519.9 480.4 443.2 411.2];

% SparkEV gear ratios
gear_ratio_2014 = 3.17;
gear_ratio_2015 = 3.87;

% maximum power displayed when accelerating hard on steep slope
global battery_power_kw; battery_power_kw = 120;
motor_power_kw = 105;

% stock tire diameter of 185/55R15
tire_ft = 23/12/2; 

% drag force data from ecomooder
global speed_mph_integer; speed_mph_integer = 0:5:90;

% mass is always assumed with 150 lb driver
mass_2014_lb = 2989 + 150;
mass_2015_lb = 2866 + 150;
mass_gvwr_lb = 3761;

% drag values from ecomodder web site in newtons
drag_2014_n = [142.13 142.13 149.64 162.15 179.67 202.19 229.71 ...
                       262.25 299.78 342.32 389.87 442.42 499.97 562.53 ...
                       630.09 702.66 780.23 862.81 950.4];
drag_2015_n = [136.66 136.66 144.17 156.68 174.2 196.72 224.24 ...
                       256.77 294.31 336.85 384.39 436.94 494.5 557.06 ...
                       624.62 697.19 774.76 857.34 944.92];
drag_gvwr_n = [169.8 169.8 177.31 189.82 207.34 229.86 257.38 ...
                       289.91 327.45 369.99 417.53 470.08 527.64 590.2 ...
                       657.76 730.33 807.9 890.48 978.06];

% some conversion factors
global feet_in_mile; global rpm_ftlb_to_hp; global hp_to_kw;
feet_in_mile = 5280; rpm_ftlb_to_hp = 5252; hp_to_kw = 0.7457;

nm_to_ftlb = 0.737562149;
newton_to_pound = 0.224809;

torque_ftlb = torque_nm * nm_to_ftlb;
driveforce_lb = torque_ftlb / tire_ft;

% polynomial for drag force so we can interpolate for higher speeds.
drag_2014_lb_poly = polyfit(speed_mph_integer, ...
                            drag_2014_n * newton_to_pound, 2);
drag_2015_lb_poly = polyfit(speed_mph_integer, ...
                            drag_2015_n * newton_to_pound, 2);
drag_gvwr_lb_poly = polyfit(speed_mph_integer, ...
                            drag_gvwr_n * newton_to_pound, 2);

% Functions to write to file

global csv_head; global csv_data;
csv_head = cellstr('speed (mph)');
csv_data = speed_mph_integer';

function fwrite_text(data)
  global fname;
  fprintf(fid, data);

function csv_data_append(header, speed_mph, data_raw)
  global speed_mph_integer; global csv_head; global csv_data;

  csv_head = [csv_head cellstr(header)];
  data = interp1(speed_mph, data_raw, speed_mph_integer)';
  csv_data = [csv_data data];

function csv_file_write(fname)
  global speed_mph_integer; global csv_head; global csv_data;

  for i=1:rows
  dlmwrite(fname, csv_data, '-append');

  csv_head = cellstr('speed (mph)');
  csv_data = speed_mph_integer';

% octave plotyy has a bug as well as not recommended for Matlab

function [ax, h1, h2] = myplotyy(x, y1, y2, alegend, alocation)
  % global EXECUTABLE
  %   yyaxis or some such
  % else % OCTAVE
    % Octave has problem with legend and data order with plotyy
    [ax,h1,h2]=plotyy( x, y1, x, y2 );
    legend(fliplr(alegend)', 'location', alocation);
    set(h2, {'Color'},get(h1,'Color'));
  % end

% find peak power and make minor adjustment to data to match spec

function [tire_power_kw,efficiency_pct] = ...
    compute_power(atitle, speed_mph, tire_ft, torque_ftlb)
  global feet_in_mile; global rpm_ftlb_to_hp; global hp_to_kw;
  global battery_power_kw;

  tire_rpm = speed_mph * feet_in_mile / 60 / (2 * pi * tire_ft);
  tire_power_kw = torque_ftlb .* tire_rpm / rpm_ftlb_to_hp * hp_to_kw;

  efficiency_pct = tire_power_kw / battery_power_kw * 100;

  csv_data_append([atitle ' tire power (kW)'], speed_mph, tire_power_kw);
  csv_data_append([atitle ' efficiency (%)'], speed_mph, efficiency_pct);

function plot_power(legend_2, speed_mph, tire_power_kw_2, efficiency_pct_2)
  global battery_power_kw;
  [ax,h1,h2]=myplotyy(speed_mph, tire_power_kw_2, efficiency_pct_2, ...
      legend_2, 'east');
  ylabel(ax(2), 'efficiency (%)'); ylabel(ax(1), 'power (kW)');
  axis(ax(2), [0 90 0 100]); axis(ax(1), [0 90 0 battery_power_kw]);
  set(ax(2), 'ytick', 0:10:100); set(ax(1), 'ytick', 0:12:battery_power_kw);
  grid on; xlabel('speed (mph)'); set(ax(1), 'xtick', 0:10:90);
  title('Power efficiency at peak torque');

  nn=2; %was for loop, but too messy
    peak_eff = max(efficiency_pct_2(:,nn));
    peak_eff_speed = speed_mph( efficiency_pct_2(:,nn) == peak_eff );
    peak_power = max(tire_power_kw_2(:,nn));
    hold on;
      plot(peak_eff_speed:peak_eff_speed, 0:battery_power_kw, 'r-');
    hold off;
    text(peak_eff_speed+1, peak_power+1, ...
      [num2str(round(peak_eff_speed*10)/10) ' mph, ' ...
        num2str(round(peak_eff*10)/10) '%, ' ...
        num2str(round(peak_power*10)/10) ' kW']);

fwrite_text('SparkEV power and efficiency\n');

% we know 2014 torque at 400 ft-lb and gear ratio. adjust torque so that
% peak torque matches specified data.
correction_torque = (400*gear_ratio_2014) / max(torque_ftlb);
torque_nm = torque_nm * correction_torque;
torque_ftlb = torque_ftlb * correction_torque;
driveforce_lb = driveforce_lb * correction_torque;

% find power with new torque value; peak power may not match, so uncorrected
atitle = 'uncorrected';
[tire_power_kw,efficiency_pct] = ...
    compute_power(atitle, speed_mph, tire_ft, torque_ftlb);
tire_power_kw_2 = tire_power_kw';
efficiency_pct_2 = efficiency_pct';
legend_2 = cellstr(atitle);

% Chevy specifies peak power of 105 kW. Expand speed to achieve that.
% It should also match 60 kW at 90MPH shown in power curve.
correction_power = motor_power_kw / max(tire_power_kw);
speed_mph = speed_mph * correction_power;

atitle = 'corrected';
[tire_power_kw,efficiency_pct] = ...
    compute_power(atitle, speed_mph, tire_ft, torque_ftlb);
tire_power_kw_2 = [tire_power_kw_2 tire_power_kw'];
efficiency_pct_2 = [efficiency_pct_2 efficiency_pct'];
legend_2 = [legend_2 cellstr(atitle)];

plot_power(legend_2, speed_mph, tire_power_kw_2, efficiency_pct_2);

% Function to find drag force for different weights
% In absense of full formula to find drag, we have to figure out how to compute
%   various drag force for different weights rather than running ecomodder
%   website each time. It seems drag polynomial coefficient for lowest order
%   changes for various weights at 1% (rolling resistance). That's totally
%   expected. Therefore, it's simply replacing lowest order coefficient with
%   1% of mass to find new drag.

function drag_force_lb = find_drag(drag_lb_poly, mass_lb, speed_mph)
  % finds new drag force for any mass
  drag_poly = drag_lb_poly;
  drag_poly(3) = mass_lb * 0.01;
  drag_force_lb = polyval(drag_poly, speed_mph);

% Function to find acceleration times, gforce, distance traveled. Note that 
%   time for speed is different from distance traveled

function [time_sec, gforce, time_dist_sec, dist_miles] = ...
         find_time_dist(driveforce_lb, mass_lb, drag_lb, speed_mph)
  ft_per_sec_to_mph = 1.4666667;
  gravity_ft_sec_sec = 32.2;

  % find gforce and speed over time
  gforce = (driveforce_lb-drag_lb) ./ (mass_lb);
  acc_mph_per_sec = gforce * gravity_ft_sec_sec / ft_per_sec_to_mph;
  speed_delta_mph = [0 ...
        (speed_mph(2:length(speed_mph)) - speed_mph(1:length(speed_mph)-1))];
  time_delta_sec = speed_delta_mph ./ acc_mph_per_sec;
  time_sec = cumsum(time_delta_sec);

  % find distance over time
  speed_extend_mph = [speed_mph ones(1, 1000)*max(speed_mph)];
  time_delta_extend_sec = [time_delta_sec ones(1, 1000)*0.1];
  distance_delta_miles = (speed_extend_mph/3600) .* time_delta_extend_sec;
  time_dist_sec = cumsum(time_delta_extend_sec);
  dist_miles = cumsum(distance_delta_miles);

% function to compute and plot single car data

function plot_one_car(car_name, mass_base_lb, drag_base_lb_poly, speed_mph, ...
  time_sec_2=[]; gforce_2=[]; time_dist_sec_2=[]; dist_miles_2=[];
  legend_2 = [];

  for nn=1:4
    if nn==1
      atitle = car_name; mass_lb = mass_base_lb;
    elseif nn==2
      atitle = [car_name ' no driver']; mass_lb = mass_base_lb - 150;
    elseif nn==3
      atitle = [car_name ' 75lb driver']; mass_lb = mass_base_lb - 75;
    elseif nn==4
      atitle = [car_name ' no drag']; mass_lb = mass_base_lb;
      drag_base_lb_poly = zeros(1, length(drag_base_lb_poly));

    drag_lb = find_drag(drag_base_lb_poly, mass_lb, speed_mph);
    [time_sec, gforce, time_dist_sec, dist_miles] = ...
        find_time_dist(driveforce_lb, mass_lb, drag_lb, speed_mph);
    csv_data_append([atitle ' time (sec)'], speed_mph, time_sec);
    csv_data_append([atitle ' gforce'], speed_mph, gforce);
    if (nn==1)
      legend_2 = cellstr(atitle);
      legend_2 = [legend_2 cellstr(atitle)];
    time_sec_2 = [time_sec_2 time_sec'];
    gforce_2 = [gforce_2 gforce'];
    time_dist_sec_2 = [time_dist_sec_2 time_dist_sec'];
    dist_miles_2 = [dist_miles_2 dist_miles'];

  [ax,h1,h2]=myplotyy(time_sec_2, gforce_2, speed_mph', legend_2, 'east');
  xlabel('time (sec)'); set(gca, 'xtick', 0:1:10);
  ylabel(ax(2), 'speed (mph)'); ylabel (ax(1), 'g force');
  miny=.15; maxy=.5;
  axis(ax(2), [0 10 0 70]); axis(ax(1), [0 10 miny maxy]); grid on;
  set(ax(2), 'ytick', 0:10:70); set(ax(1), 'ytick', miny:(maxy-miny)/7:maxy);
  title([car_name ' acceleration speed vs time']);

  max_x = 20;
  figure; plot(time_dist_sec_2, dist_miles_2);
  xlabel('time (sec)'); ylabel('distance (miles)');
  axis([0 max_x 0 0.3]); grid on;
  set(gca, 'xtick', 0:max_x); set(gca, 'ytick', 0:0.025:0.3);
  title([car_name ' acceleration distance vs time']);
  legend(legend_2, 'location', 'east');

% find data for 2014

plot_one_car('2014', mass_2014_lb, drag_2014_lb_poly, speed_mph, driveforce_lb);

% run data for all mass, from -1000 lb to +700 lb based on 2015 mass

% function to plot acceleration for various weights
function plot_acc_over_weights(atitle, speed_mph, mass_min_lb, mass_max_lb, ...
    drag_base_lb_poly, driveforce_lb)
  mass_lb_2=[]; time_sec_2=[]; gforce_2=[]; time_dist_sec_2=[]; dist_miles_2=[];
  for mass_lb=[mass_min_lb:100:mass_max_lb]
    drag_lb = find_drag(drag_base_lb_poly, mass_lb, speed_mph);
    [time_sec, gforce, time_dist_sec, dist_miles] = ...
        find_time_dist(driveforce_lb, mass_lb, drag_lb, speed_mph);

    mass_lb_2 = [mass_lb_2 mass_lb];
    time_sec_2 = [time_sec_2 time_sec'];
    gforce_2 = [gforce_2 gforce'];
    time_dist_sec_2 = [time_dist_sec_2 time_dist_sec'];
    dist_miles_2 = [dist_miles_2 dist_miles'];

    csv_data_append([num2str(mass_lb) 'lb time (sec)'], speed_mph, time_sec);
    csv_data_append([num2str(mass_lb) 'lb gforce'], speed_mph, gforce);

  [ax,h1,h2]=myplotyy(time_sec_2, gforce_2, speed_mph', ...
      cellstr(num2str(mass_lb_2'))', 'east');
  grid on; xlabel('time (sec)'); set(gca, 'xtick', 0:1:10);
  ylabel(ax(2), 'speed (mph)'); ylabel (ax(1), 'g force');

  if mass_min_lb > 3000
    axis(ax(2), [0 10 0 70]); set(ax(2), 'ytick', 0:10:70);
    miny=0.15; maxy=0.5; divy=7;
    axis(ax(2), [0 10 0 90]); set(ax(2), 'ytick', 0:10:90);
    miny=.15; maxy=0.69; divy=9;
  axis(ax(1), [0 10 miny maxy]);
  set(ax(1), 'ytick',miny:((maxy-miny)/divy):maxy);

atitle = '2015 SparkEV acceleration for weight gain speed vs time';
plot_acc_over_weights(atitle, ...
    speed_mph, mass_2015_lb, mass_2015_lb+700, ...
    drag_2015_lb_poly, driveforce_lb);
fwrite_text([atitle '\n']); csv_file_write(fname);

atitle = '2015 SparkEV acceleration for weight reduction speed vs time';
plot_acc_over_weights(atitle, ...
    speed_mph, mass_2015_lb-1000, mass_2015_lb-100, ...
    drag_2015_lb_poly, driveforce_lb);
fwrite_text([atitle '\n']); csv_file_write(fname);

% acceleration over hills

function plot_hill_acceleration(atitle, speed_mph, mass_lb, ...
    drag_base_lb_poly, driveforce_lb)
  time_sec_2=[]; gforce_2=[]; time_dist_sec_2=[]; dist_miles_2=[];
  for grade_pct=[0 1 2 3 5 8 13 21]
    hill_angle = atan(grade_pct/100);
    mass_normal = mass_lb * cos(hill_angle);

    drag_lb = find_drag(drag_base_lb_poly, mass_normal, speed_mph);
    hill_driveforce_lb = driveforce_lb - mass_lb * sin(hill_angle);
    [time_sec, gforce, time_dist_sec, dist_miles] = ...
        find_time_dist(hill_driveforce_lb, mass_lb, drag_lb, speed_mph);

    %zero out negative values due to too steep hill
    time_sec(gforce<0) = 1000; gforce(gforce<0) = 0;
    time_sec(time_sec<0) = 1000; gforce(time_sec<0) = 0;

    grade_pct_2 = [grade_pct_2 grade_pct];
    time_sec_2=[time_sec_2 time_sec'];
    gforce_2=[gforce_2 gforce'];
    time_dist_sec_2=[time_dist_sec_2 time_dist_sec'];
    dist_miles_2=[dist_miles_2 dist_miles'];

    csv_data_append([num2str(grade_pct) '% grade time (sec)'], ...
        speed_mph, time_sec);
    csv_data_append([num2str(grade_pct) '% grade gforce'], speed_mph, gforce);

  [ax,h1,h2]=myplotyy(time_sec_2, gforce_2, speed_mph', ...
      cellstr(num2str(grade_pct_2'))', 'east');
  xlabel('time (sec)');   set(gca, 'xtick', 0:2:20); grid on;
  ylabel(ax(2), 'speed (mph)'); ylabel (ax(1), 'g force');
  axis(ax(2), [0 20 0 70]); set(ax(2), 'ytick', 0:10:70);

  miny=0; maxy=0.49; divy=7;
  axis(ax(1), [0 20 miny maxy]);
  set(ax(1), 'ytick',miny:((maxy-miny)/divy):maxy);

atitle = '2015 SparkEV acceleration over % grades speed vs time';
plot_hill_acceleration(atitle, speed_mph, mass_2015_lb, ...
    drag_2015_lb_poly, driveforce_lb);
fwrite_text([atitle '\n']); csv_file_write(fname);

% climbing ability for various weights

function plot_climbing(atitle, speed_mph, mass_2015_lb, drag_base_lb_poly, ...
  mass_lb_2=[]; climb_pct_2=[];
  for mass_delta=[0:100:700]
    mass_lb = mass_2015_lb+mass_delta;
    drag_lb = find_drag(drag_base_lb_poly, mass_lb, speed_mph);

    force_lb = driveforce_lb - drag_lb;
    climb_pct = tan(asin((force_lb)/(mass_lb))) * 100;

    %Chevy specifies max hill start as 25% at 0 MPH

    mass_lb_2 = [mass_lb_2; mass_lb];
    climb_pct_2 = [climb_pct_2 climb_pct'];

    csv_data_append([num2str(mass_lb) 'lb climb (%)'], speed_mph, climb_pct);

  figure; plot( speed_mph, climb_pct_2);
  xlabel('speed (mph)'); ylabel('grade (%)');
  axis([0 90 0 50]); grid on;
  set(gca, 'xtick', 0:5:90); set(gca, 'ytick', 0:5:50);
  legend(cellstr(num2str(mass_lb_2)), 'location', 'northeast');

atitle = '2015 SparkEV climbing ability vs speed over weight';
plot_climbing(atitle, speed_mph, mass_2015_lb, drag_2015_lb_poly, ...
fwrite_text([atitle '\n']); csv_file_write(fname);

% Bolt guess

bolt_peak_motor_torque_ftlb = 266;
bolt_gear_ratio = 7.05;
bolt_tire_radius_ft = 25.5 / 12 / 2;
bolt_mass_lb = 3580 + 150;
bolt_peak_power_kw = 150;

% find the speed at which Bolt's peak power will occur
bolt_rpm_to_mph = (2*pi*bolt_tire_radius_ft/feet_in_mile) / bolt_gear_ratio* 60;
bolt_peak_motor_power_rpm = ...
    rpm_ftlb_to_hp * bolt_peak_power_kw/hp_to_kw / bolt_peak_motor_torque_ftlb;
bolt_peak_motor_power_mph = bolt_peak_motor_power_rpm * bolt_rpm_to_mph;

% SparkEV torque to Bolt by simply scaling
bolt_torque_ftlb = bolt_peak_motor_torque_ftlb * bolt_gear_ratio * ...
                   (torque_ftlb / max(torque_ftlb));
bolt_driveforce_lb = bolt_torque_ftlb / bolt_tire_radius_ft;

plot_one_car('bolt', bolt_mass_lb, drag_2015_lb_poly, speed_mph, bolt_driveforce_lb);

% find quickest gear ratio for speeds by going through all ratios

function find_best_gear_ratio(drag_lb_poly, mass_lb, speed_mph, driveforce_lb)
  time_max_30mph=[]; time_max_60mph=[];
  peak_speeds = 31:90;
  for peak_speed=peak_speeds
    gear_ratio = peak_speed / 90;
    gear_driveforce_lb = driveforce_lb / gear_ratio;
    gear_speed_mph = speed_mph * gear_ratio;
    drag_lb = find_drag(drag_lb_poly, mass_lb, gear_speed_mph);
    [time_sec, gforce, time_dist_sec, dist_miles] = ...
        find_time_dist(gear_driveforce_lb, mass_lb, drag_lb, gear_speed_mph);
    gear_int_speed_mph = 0:fix(max(gear_speed_mph));
    gear_int_time_sec = interp1(gear_speed_mph, time_sec, gear_int_speed_mph);
    time_max_30mph = [time_max_30mph gear_int_time_sec(gear_int_speed_mph==30)];
    if (max(gear_int_speed_mph) < 60)
      time_max_60mph = [time_max_60mph 0];
      time_max_60mph = [time_max_60mph gear_int_time_sec(gear_int_speed_mph==60)];

  subplot(2, 1, 1); plot(peak_speeds, time_max_30mph, 'o-');
  xlabel('peak speed (mph)'); ylabel('time (sec)');
  axis([31 37 1.55 1.63]); grid on;
  title('best peak speed gearing for 30 MPH acceleration');
  subplot(2, 1, 2); plot( peak_speeds, time_max_60mph, 'o-');
  xlabel('peak speed (mph)'); ylabel('time (sec)');
  axis([60 66 6.56 6.64]); grid on;
  title('best peak speed gearing for 60 MPH acceleration');

find_best_gear_ratio(drag_2015_lb_poly, mass_2015_lb, speed_mph, driveforce_lb)

% find theoretical maximum speed with gear change

function plot_maxspeed_with_gear_chage(speed_mph, driveforce_lb, drag_poly, ...
  ext_min_speed = 88; ext_max_speed=105; ext_plot_min_speed = 75;
  ext_speed_mph = ext_min_speed:ext_max_speed;
  ext_driveforce_poly = polyfit(speed_mph(speed_mph > ext_min_speed), ...
      driveforce_lb(speed_mph > ext_min_speed), 1);
  ext_driveforce_lb = polyval(ext_driveforce_poly, ext_speed_mph);

  ext_drag_speed = ext_plot_min_speed:ext_max_speed;
  ext_drag_lb = find_drag(drag_poly, mass_lb, ext_drag_speed);

  plot(speed_mph(speed_mph > ext_plot_min_speed), 
      driveforce_lb(speed_mph > ext_plot_min_speed), 'bo-');
  hold on;
    plot(ext_speed_mph, ext_driveforce_lb, 'go-');
    plot(ext_drag_speed, ext_drag_lb, 'ro-');
  hold off;

  xlabel('speed (mph)'); ylabel('force (lb)');
  axis([75 105 0 600]); grid on;
  title('SparkEV theoretical peak speed');
  legend('drive force', 'extended drive force', 'drag force');

plot_maxspeed_with_gear_chage(speed_mph, driveforce_lb, drag_2015_lb_poly, ...