#!/usr/bin/python2 import math # Customize me! all_electric = False # Whether or not this is a Typ-1e (True) or a Typ-1h (false) initial_charge = None # If you'd rather specify the initial charge by a number of watt-hours, enter it here. Otherwise, set it to "None". initial_charge_percent = 1.00 # If you'd rather specify by a %, enter it here (50% = 0.50, 100% = 1.00, etc). If not, set this to "None". target_speed = 80.0 # Miles per hour vehicle_weight = 2000.0 # Pounds, loaded route = "LA Reservoir to Grapevine, CA" # Name of route to take; see "routes" list below. # Vehicle constants -- adjust as more info comes out min_generator_charge = 1000.0 # Watt hours; generator starts up if charge drops below here max_generator_charge = 1200.0 # Watt hours; generator shuts off if charge increases beyond here max_typ1h_capacity = 6000.0 # Watt hours; Typ-1h battery pack can't hold more than this. max_typ1e_capacity = 10000.0 # Watt hours; Typ-1e battery pack can't hold more than this. regen_efficiency = 0.8 # Decimal percentage (0.8 = 80%) generator_power = 12000.0 # Watts # Vehicle power consumption curve consumption_at_speed = [ # mph, Wh/mi (55.0, 83.333), (80.0, 142.857) ] # Automatic speed limitation (turtling) at various SOCs max_speed = [ #Wh, mph (30.0, 45.0), (60.0, 50.0), (90.0, 55.0), (120.0, 60.0), (180.0, 65.0), (880.0, 70.0), (910.0, 75.0), (940.0, 80.0), (970.0, 85.0), (1000.0, 90.0) ] # Pre-defined routes routes = { # Format: lat, lon, feet_alt "LA Reservoir to Grapevine, CA": [ (34.2348, -118.4119, 865.0), (34.2548, -118.4332, 944.0), (34.2774, -118.4539, 1016.0), (34.3003, -118.4771, 1173.0), (34.3141, -118.4888, 1268.0), (34.3269, -118.5032, 1366.0), (34.3411, -118.5206, 1782.0), (34.3494, -118.5399, 1500.0), (34.3576, -118.5528, 1402.0), (34.3945, -118.5712, 1399.0), (34.4311, -118.5883, 1057.0), (34.4604, -118.6152, 1059.0), (34.4898, -118.6191, 1174.0), (34.5155, -118.6353, 1456.0), (34.5365, -118.6470, 1895.0), (34.5574, -118.6759, 2290.0), (34.5712, -118.6892, 2591.0), (34.5897, -118.7115, 2841.0), (34.6088, -118.7117, 2806.0), (34.6313, -118.7271, 2663.0), (34.6419, -118.7498, 2666.0), (34.6699, -118.7651, 2774.0), (34.6833, -118.7823, 2815.0), (34.7040, -118.7943, 2864.0), (34.7296, -118.7985, 3017.0), (34.7594, -118.7959, 3252.0), (34.7862, -118.8264, 3611.0), (34.7957, -118.8569, 3882.0), (34.8070, -118.8821, 4050.0), (34.8476, -118.8688, 3422.0), (34.8749, -118.8923, 3177.0), (34.9038, -118.9232, 2351.0), (34.9421, -118.9313, 1495.0) ], "Grapevine, CA to LA Reservoir": [ # lat, lon, feet_alt (34.9421, -118.9313, 1495.0), (34.9038, -118.9232, 2351.0), (34.8749, -118.8923, 3177.0), (34.8476, -118.8688, 3422.0), (34.8070, -118.8821, 4050.0), (34.7957, -118.8569, 3882.0), (34.7862, -118.8264, 3611.0), (34.7594, -118.7959, 3252.0), (34.7296, -118.7985, 3017.0), (34.7040, -118.7943, 2864.0), (34.6833, -118.7823, 2815.0), (34.6699, -118.7651, 2774.0), (34.6419, -118.7498, 2666.0), (34.6313, -118.7271, 2663.0), (34.6088, -118.7117, 2806.0), (34.5897, -118.7115, 2841.0), (34.5712, -118.6892, 2591.0), (34.5574, -118.6759, 2290.0), (34.5365, -118.6470, 1895.0), (34.5155, -118.6353, 1456.0), (34.4898, -118.6191, 1174.0), (34.4604, -118.6152, 1059.0), (34.4311, -118.5883, 1057.0), (34.3945, -118.5712, 1399.0), (34.3576, -118.5528, 1402.0), (34.3494, -118.5399, 1500.0), (34.3411, -118.5206, 1782.0), (34.3269, -118.5032, 1366.0), (34.3141, -118.4888, 1268.0), (34.3003, -118.4771, 1173.0), (34.2774, -118.4539, 1016.0), (34.2548, -118.4332, 944.0), (34.2348, -118.4119, 865.0) ] } max_capacity = max_typ1h_capacity # If electric... if all_electric == True: generator_power = 0.0 max_capacity = max_typ1e_capacity max_speed = [ (0.0, 90.0) ] # If they want to set initial charge by a percentage if initial_charge_percent != None: initial_charge = max_capacity * initial_charge_percent # Other constants miles_to_meters_multiplier = 1609.334 feet_to_meters_multiplier = 0.3048 hours_to_seconds_multiplier = 3600.0 pounds_to_kilograms_multiplier = 0.45359237 watt_hours_to_joules_multiplier = 3600.0 earth_gravity_constant = 9.81 # Calculated constants vehicle_mass = vehicle_weight * pounds_to_kilograms_multiplier # Functions def WhPerMi_at_speed(mph): # Slope-intercept last_speed = 0 last_wh = 0 for entry in consumption_at_speed: speed = entry[0] wh = entry[1] if mph < speed: percent = (mph - last_speed) / (speed - last_speed) return percent * wh + (1.0 - percent) * last_wh last_speed = speed last_wh = wh #Fallthrough last_speed = consumption_at_speed[-2][0] last_wh = consumption_at_speed[-2][1] speed = consumption_at_speed[-1][0] wh = consumption_at_speed[-1][1] percent = (mph - last_speed) / (speed - last_speed) return percent * wh + (1.0 - percent) * last_wh def max_speed_at_SOC(cur_charge): last_charge = 0 last_mph = 0 for entry in max_speed: charge = entry[0] mph = entry[1] if cur_charge < charge: percent = (cur_charge - last_charge) / (charge - last_charge) return percent * mph + (1.0 - percent) * last_mph last_charge = charge last_mph = mph #Fallthrough return max_speed[-1][1] def coords_to_ground_distance(lat1, lon1, lat2, lon2): R = 6371000.0 dLat = math.radians(lat2 - lat1) dLon = math.radians(lon2 - lon1) a = math.sin(dLat / 2) * math.sin(dLat / 2) + \ math.cos(math.radians(lat1)) * math.cos(math.radians(lat2)) * \ math.sin(dLon / 2) * math.sin(dLon / 2) c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a)) d = R * c return d def energy_to_climb(meters): return meters * vehicle_mass * earth_gravity_constant / watt_hours_to_joules_multiplier def translate_route(route): translated_route = [] last_lat = route[0][0] last_lon = route[0][1] cumulative_dist = 0 for entry in route: lat = entry[0] lon = entry[1] alt = entry[2] * feet_to_meters_multiplier dist = coords_to_ground_distance(last_lat, last_lon, lat, lon) cumulative_dist += dist translated_route.append( (cumulative_dist, alt) ) last_lat = lat last_lon = lon return translated_route def simulate(route, starting_charge, target_speed, feet_per_cycle): route = translate_route(route) meters_per_cycle = feet_per_cycle * feet_to_meters_multiplier dist = 0 generator_on = False charge = starting_charge last_alt = route[0][1] route_last = 0 route_next = 1 while True: dist += meters_per_cycle if dist > route[-1][0]: break if dist > route[route_next][0]: route_last += 1 route_next += 1 percent = (dist - route[route_last][0]) / (route[route_next][0] - route[route_last][0]) alt = percent * route[route_next][1] + (1.0 - percent) * route[route_last][1] alt_change = alt - last_alt last_alt = alt #Try to find a speed that won't go negative in terms of charge. speed = target_speed max_speed = max_speed_at_SOC(charge) if speed > max_speed: speed = max_speed while True: temp_charge = charge if alt_change >= 0: temp_charge -= energy_to_climb(alt_change) else: temp_charge -= energy_to_climb(alt_change) * regen_efficiency travel_dist = math.sqrt(alt_change * alt_change + meters_per_cycle * meters_per_cycle) temp_charge -= (travel_dist / miles_to_meters_multiplier) * WhPerMi_at_speed(speed) if temp_charge < min_generator_charge: generator_on = True elif temp_charge > max_generator_charge: generator_on = False if generator_on: hours_passed = (travel_dist / miles_to_meters_multiplier) / speed temp_charge += generator_power * hours_passed if temp_charge > max_capacity: temp_charge = max_capacity if temp_charge < 0: speed -= 1.0 #If we go below zero, try again, a touch slower. continue else: break charge = temp_charge if all_electric == True: print "Mile %.3f: Charge=%d Wh, Speed=%d mph" % (dist / miles_to_meters_multiplier, charge, speed) else: print "Mile %.3f: Charge=%d Wh, Speed=%d mph; Generator running: %s" % (dist / miles_to_meters_multiplier, charge, speed, generator_on) # Runme simulate(routes[route], initial_charge, target_speed, 100.0)