Skip to content
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

Merged
merged 19 commits into from
Nov 28, 2023
Merged

Ndr/phev #33

merged 19 commits into from
Nov 28, 2023

Conversation

nreinicke
Copy link
Collaborator

@nreinicke nreinicke commented Nov 16, 2023

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 dunno, does that really cover what happens in PHEVs? i mean, couldn't the charge sustaining model recharge the battery enough that the PHEV switches back to battery again later in the trip, perhaps multiple times? i think we want to get that right if we're going to model PHEV, otherwise it will miss out on the benefits of regenerative braking on long-distance trips. which, also btw, implies we want to capture regen from the powertrain model, so we want to be sure the models produce those kinds of outputs for downhill segments.

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:

{
    "origin_name": "Government Center Station",
    "destination_name": "Cannonball Creek Brewery",
    "origin_x": -105.200146,
    "origin_y": 39.726570,
    "destination_x": -105.234964,
    "destination_y": 39.768477,
    "model_name": "2016_CHEVROLET_Bolt",
    "phev_mode": "true",
    "grid_search": {
        "energy_cost_coefficient": [0.0, 1.0]
    }
}

@robfitzgerald
Copy link
Collaborator

In our weekly RouteE meeting, we discussed doing this very simple implementation to start

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.

@nreinicke
Copy link
Collaborator Author

nreinicke commented Nov 17, 2023

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
    }
}

@robfitzgerald
Copy link
Collaborator

a few questions right off the bat:

  • your example has a struct EnergyTraversalModel, is this intended to be a replacement for SpeedGradeModel that generalizes the powertrain?
  • your suggested Powertrain trait has a lot in common with the SpeedGradePredictionModel trait, is it worth merging the two into one, or is it still helpful to separate prediction from the powertrain?
  • why do we have separate models for charge sustaining and charge depleting?
  • you mention "The battery model will currently return a negative number but I'm capping it to zero since we can't currently handle negative cost" - but cost isn't the same thing as state, so, why do we need to cap this?

@nreinicke
Copy link
Collaborator Author

your example has a struct EnergyTraversalModel, is this intended to be a replacement for SpeedGradeModel that generalizes the powertrain?

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).

your suggested Powertrain trait has a lot in common with the SpeedGradePredictionModel trait, is it worth merging the two into one, or is it still helpful to separate prediction from the powertrain?

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)

why do we have separate models for charge sustaining and charge depleting?

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.

You mention "The battery model will currently return a negative number but I'm capping it to zero since we can't currently handle negative cost" - but cost isn't the same thing as state, so, why do we need to cap this?

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.

@nreinicke nreinicke mentioned this pull request Nov 20, 2023
@nreinicke
Copy link
Collaborator Author

Okay, I've done quite a bit of modification to this and have create a new Vehicle abstraction that implements a predict_energy() method. Now, the energy traversal model just holds onto a vehicle that could have any way of predicting energy (right now just conventional and plugin in hybrid).

This leaves open a few items that I'll convert to issues if/when we roll this in:

  • create a "battery electric" vehicle in addition to the existing "conventional" and "plug_in_hybrid" that also keeps track of its battery SOC.
  • expose the conversion between gasoline and electricity to the config
  • add in a new diesel energy rate unit and update the config for all diesel vehicles
  • update the internal configs to support this change

Copy link
Collaborator

@robfitzgerald robfitzgerald left a 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 {
Copy link
Collaborator

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)]
Copy link
Collaborator

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.

Copy link
Collaborator Author

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

Copy link
Collaborator

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 {
Copy link
Collaborator

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 {
Copy link
Collaborator

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",
Copy link
Collaborator

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.

Copy link
Collaborator Author

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 {
Copy link
Collaborator

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

  1. https://www.lawinsider.com/dictionary/conventional-vehicle

Copy link
Collaborator Author

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?

Copy link
Collaborator

@robfitzgerald robfitzgerald Nov 27, 2023

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?

Copy link
Collaborator

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

Copy link
Collaborator Author

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).

Copy link
Collaborator Author

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 {
Copy link
Collaborator

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],
Copy link
Collaborator

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.

@robfitzgerald
Copy link
Collaborator

wow, conflicts don't look bad at all!

@nreinicke
Copy link
Collaborator Author

nreinicke commented Nov 27, 2023

@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 SingleFuelVehicle and DualFuelVehicle but I think this merits more discussion).

I also left a few items for future work that I'll put into new issues:

  • expose the conversion between gasoline and electricity to the config
  • add in a new diesel energy rate unit and update the config for all diesel vehicles
  • update the internal configs to support this change

Copy link
Collaborator

@robfitzgerald robfitzgerald left a 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 {
Copy link
Collaborator

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()

Copy link
Collaborator Author

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).

@nreinicke nreinicke merged commit 6f6411b into main Nov 28, 2023
5 checks passed
@nreinicke nreinicke deleted the ndr/phev branch November 28, 2023 16:29
@nreinicke nreinicke mentioned this pull request Nov 28, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants