-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Ndr/phev #33
Conversation
ok, yea, that helps explain how this feature came about, that the group was asking for exactly this. that's what i get for missing a week on sick leave 🤮 as usual i'm trying to think about how we extend the design to more general problems, but i'll try and reel that impulse in a little. but if we make a special case for PHEV on our otherwise generalized energy-based traversal model, we'll have to build around it and then may regret it later when regenerative braking is requested, so i hope you don't mind if i still plan to take a little time with this when i return on monday. i hope that's ok timing for a review. |
Oh yeah of course, there wasn't any rush to get this implemented, it's just something we discussed as a possible next step. It will be nice to get this right and try to make it as general as possible. I think the crux of making this general is that the state representation and energy calculation varies depending on the powertrain type. Maybe we need a new abstraction here to represent the powertrain specific logic? Something like: pub struct EnergyTraversalModel {
pub service: Arc<EnergyModelService>,
pub powertrain: Arc<dyn Powertrain>,
pub energy_cost_coefficient: f64,
}
impl TraversalModel for EnergyTraversalModel {
fn initial_state(&self) -> TraversalState {
self.powertrain.initial_state()
}
fn traversal_cost(
&self,
_src: &Vertex,
edge: &Edge,
_dst: &Vertex,
state: &TraversalState,
) -> Result<TraversalResult, TraversalModelError> {
let energy = self.powertrain.compute_energy(&edge, &state);
// mix energy and cost
}
} |
a few questions right off the bat:
|
yeah that was my idea with an eye towards eventually generalizing other aspects of it like which features it uses (maybe speed, grade, and some other important feature that we haven't found yet).
That's a good question and I'm not totally sure but I think it still makes sense to keep these distinct so we can keep the separation between how the prediction model is represented as code (i.e. smartcore / onnx / another ML library) and the logic of how we use the results from the prediction model (PHEV, EV, etc)
That's a function of how fastsim represents the models, there are separate models for when the PHEV is operating on battery only and when the PHEV is using both the gasoline and battery together. So then we also need two separate RouteE Powertrain models to capture each of these operating modes and build our own "control logic" for when to apply each one.
That's a good point. We could just cap the contribution of the energy to the cost but still allow a negative cost to contribute directly to the state. This still wouldn't help the algorithm find shortest paths that take advantage of regen braking since it wouldn't directly see those benefits in the cost but it would result in a more realistic battery SOC as the vehicle traverses. |
Okay, I've done quite a bit of modification to this and have create a new This leaves open a few items that I'll convert to issues if/when we roll this in:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
great. thanks for putting all the care into how we can build these vehicle types into compass, i think you're clearly the expert of us on how to deal with this stuff.
i like the &[T]
state representation, that could shave off a bunch of memory during search.
there's a few requests for changes here, but in general i'm real happy to see that this came together without any deep changes.
pub struct SpeedGradeModel { | ||
pub service: Arc<SpeedGradeModelService>, | ||
pub model_record: Arc<SpeedGradePredictionModelRecord>, | ||
pub struct EnergyTraversalModel { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i like the name change with the new abstraction
} | ||
} | ||
|
||
#[cfg(test)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'm noticing that these tests don't do equality assertions. is it possible for us to design it so that the result has an exact expected value, and that those values make sense? just something so we feel comfortable that the math is good.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah it's tricky since the vehicle model might not return the exact energy each time (especially if we swap it out with a new model) but perhaps we can add some "order of magnitude" test to make sure it's in a reasonable range
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
got it. well, i'd follow your lead if this is a requirement for this. i think, maybe knowing it's in the right ballpark could give us a little reassurance our units are correct but then again we don't want to get into the business of writing tests here that are really routee-powertrain tests (tests of the underlying model).
|
||
pub type VehicleState = Vec<StateVar>; | ||
|
||
pub struct VehicleEnergyResult { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pull out to it's own file
} | ||
|
||
/// A struct to hold the prediction model and associated metadata | ||
pub struct PredictionModelRecord { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pull out into it's own file
@@ -29,6 +31,8 @@ impl CompassConfigurationField { | |||
CompassConfigurationField::Plugins => "plugin", | |||
CompassConfigurationField::InputPlugins => "input_plugins", | |||
CompassConfigurationField::OutputPlugins => "output_plugins", | |||
CompassConfigurationField::ChargeDeplete => "charge_deplete", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this be "charge_depleting" and "charge_sustaining"? i don't see us use these contracted versions of these terms anywhere else nor have i heard people say them like this but you def know better than i.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that's a good point, it should be consistent with what we've been using elsewhere, I'll modify
config_json_extension::ConfigJsonExtensions, | ||
}; | ||
|
||
pub trait VehicleBuilder { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
two thoughts here.
enum?
this looks a lot like an enum. maybe:
pub enum VehicleBuilder {
ConventionalVehicleBuilder,
PlugInHybridBuilder
}
impl VehicleBuilder {
pub fn build() {}
}
names
"conventional" and "plugin hybrid" names.. i was reviewing those implementations, and, what i felt like i was looking at was 1) "a simple vehicle prediction model wrapper" and 2) "a hybrid or plugin hybrid where there are two models that tradeoff based on a fuel level". i dunno, the word "conventional" is somewhat synonymous with combustion vehicles 1, maybe we can think of a different name since it can be ICE or BEV here, "SinglePowertrainVehicle" or something like that? and maybe the second is "HybridVehicle" because it could be either kind of hybrid?
Footnotes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah good point, conventional is misleading since it can be any powertrain type that just reports a single fuel. Something tricky here is that the hybrid models also just return fuel in gasoline and are suitable for this "conventional" model. The "plugin hybrid" is just a special case for a PHEV vehicle. So, we have a weird setup where we need to find a name for the model that represents the set {ICE, BEV, HEV} and the set {PHEV}. Maybe something like "SingleFuelVehicle" and "DualFuelVehicle" that represents the fuel that goes into the vehicle?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something tricky here is that the hybrid models also just return fuel in gasoline and are suitable for this "conventional" model
but, is that an implementation detail of how we're currently talking about hybrid, or an implementation detail of how FASTSim does it? i thought our long-term goal might be that these vehicles can refuel. if we don't want to re-write everything for that behavior, then, i would assume hybrid would be a special case of the PHEV code where there simply is no refuel behavior (edit: "at stations; and there would be refuel from regenerative braking")
as for names, something like SingleFuelVehicleModel and HybridFuelVehicleModel might be general enough?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it can be any powertrain type that just reports a single fuel
here's another angle i can talk about my confusion here. i think i'm not clear on what the meaning of the Vehicle abstraction is. i thought it was similar to our HIVE Mechatronics, but including the fuel level, so something that represents all the physics of how energy is modeled for a vehicle.
in the abstraction hierarchy, above Vehicle, we have the TraversalModel, which may have different Vehicles. below it, we have the PredictionModel, which may appear as one or two parts of a Vehicle. it feels like we have the right layers here.
so, maybe we need agreement on what Vehicles represent. i thought it was something like (ideally)
- how energy is consumed when idling (access costs, eventually)
- how energy is consumed when driving (traversal costs)
- how a fuel level is modified by those activities (+-)
- how fuel is refilled at stations (access behavior, eventually)
- in hybrid systems, how fuel source/powertrain switching behavior occurs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah this is a good point that we need to come up with a good definition for what a Vehicle is. My thinking is starting to trend towards treating a Vehicle as something like a representation of a set of specific vehicle models that share operating behavior. So, if we were to follow that definition, maybe we would actually have vehicle models for each of [ICE, BEV, HEV, PHEV] since they each exhibit different behavior with respect to how they operate (specifically in the domain of consuming and gaining energy).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something that the above leaves out is that there will still be shared behavior between some of the models. To address that, maybe we can just centralize the shared behavior into functions that are shared between the different vehicles.
pub updated_state: VehicleState, | ||
} | ||
|
||
pub trait Vehicle: Send + Sync { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
two thoughts:
names
i don't think we want to pluralize module names. could we maybe make the module "vehicle" and call this trait "VehicleModel" and file "vehicle_model.rs"? or something like that.
documentation
since this is part of the developer API of Compass, we should be sure to add rust docstrings to this trait. let's mention how these methods relate to the methods of TraversalModel.
speed: (Speed, SpeedUnit), | ||
grade: (Grade, GradeUnit), | ||
distance: (Distance, DistanceUnit), | ||
state: &[StateVar], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh interesting, &[T]
vs Vec[T]
vs Box<[T]>
. we have so many of these, millions at runtime, that picking the right one for how we reference state is going to make a big difference. and given that they are (1) a fixed size, 2) immutable, 3) we don't use any special features of Vec
, i think it's a real good call to move to a slice, and just a reference of a slice is probably sufficient. perhaps something we can change everywhere soon.
wow, conflicts don't look bad at all! |
@robfitzgerald I've made an attempt to address the comments that you've made here. With respect to the Vehicle abstraction, perhaps we can spin a new issue out of this to address that? (in the meantime I updated the names to I also left a few items for future work that I'll put into new issues:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cool, exciting to see these more granular vehicle types rolling into the search and to be modeling them with greater realism. thanks for making the big lift!
.energy_rate_unit | ||
.associated_energy_unit(); | ||
|
||
if battery_soc_percent > 0.0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
assume we can just use the battery
i think logic like this would avoid the need to make that assumption:
if soc == 0:
energy = 0.0
use_gas()
else:
energy = predict() # use battery
if energy < 0:
energy = 0.0
use_gas()
else:
use_battery()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that's a good point, I think I'll add this into a new issue to upgrade the logic of the PHEV in general to be more accurate (including doing things like spitting electric and gas on the same link if we cross the battery threshold during that traversal).
Starts to implement a new PHEV traversal model described in #31.
This uses the simpler approach to deplete the battery first and then switch to gasoline for the remainder of the trip. This also doesn't consider if the vehicle runs out of battery mid-link but rather just gets to the end of the link and then would switch to gasoline on the next link.
@robfitzgerald also mentions in #31:
I think it depends on the mode that the PHEV is operating under which varies based on the model of PHEV and their control implementation. In our weekly RouteE meeting, we discussed doing this very simple implementation to start but I agree that it doesn't capture the true nature of the PHEV. In order to get closer to that we would probably have to allow negative electrical energy from the battery model (charge depleting) and the gasoline model (charge sustaining). The battery model will currently return a negative number but I'm capping it to zero since we can't currently handle negative cost. The gasoline model is not returning electrical energy (just gasoline) and so we would need to modify routee-powertrain to allow that.. Once that is implemented we could capture regen braking during both modes and feed it back into the battery.
I also implemented the battery capacity as a config level parameter and not a query level parameter under the premise that it is very vehicle dependent and the user might not know that information.
The query for this mode would look something like this: