The charging formulation: the assignment of vehicles to chargers and the charger power limits. It receives the electric vehicle formulation it couples to, and is outside the power balance.
Chargers: assign electric vehicles to chargers and cap their power by the charger's rating.
It couples to the ElectricVehicleFormulation it receives: the vehicles' power
variables and trip schedule. Chargers inject nothing into the power balance;
the vehicles do.
Source code in src/odys/optimization/formulations/charging.py
classChargingFormulation(VariableFormulation[ChargingVariables]):"""Chargers: assign electric vehicles to chargers and cap their power by the charger's rating. It couples to the `ElectricVehicleFormulation` it receives: the vehicles' power variables and trip schedule. Chargers inject nothing into the power balance; the vehicles do. """dimension=CHARGERentity_type=Chargerassignment_name:ClassVar[str]="charger_ev_assignment"def__init__(self,chargers:Sequence[Charger],context:ModelContext,electric_vehicles:ElectricVehicleFormulation,)->None:"""Initialize with the chargers of the system and the electric vehicles they serve. Args: chargers: The chargers, at least one. context: The shared indexing of the problem. electric_vehicles: The formulation of the vehicles the chargers serve. """super().__init__(chargers,context)self.arrays=vectorize(ChargerArrays,chargers,self.coordinates)self.electric_vehicles=electric_vehicles@classmethoddefbuild(cls,inputs:FormulationInputs)->Self|None:"""Return the formulation of the chargers, or None if there are none. Raises: OdysError: If there are chargers but no electric vehicle formulation was built before. """chargers=inputs.of_type(Charger)ifnotchargers:returnNoneelectric_vehicles=inputs.formulation_of(ElectricVehicleFormulation)ifelectric_vehiclesisNone:msg="ChargingFormulation must be built after an ElectricVehicleFormulation."raiseOdysError(msg)returncls(chargers,inputs.context,electric_vehicles)def_create_variables(self,model:linopy.Model)->ChargingVariables:"""Add the binary assignment of each vehicle to each charger, per scenario and time."""coords=self.context.variable_coords(self.coordinates,self.electric_vehicles.coordinates)returnChargingVariables(assignment=model.add_variables(name=self.assignment_name,coords=coords,binary=True),)@constraintdef_get_charger_one_ev_per_charger_constraint(self)->ModelConstraint:"""Each charger serves at most one vehicle at a time."""returnModelConstraint(constraint=self.variables.assignment.sum(EV)<=1,name="charger_one_ev_per_charger_constraint",)@constraintdef_get_charger_one_charger_per_ev_constraint(self)->ModelConstraint:"""Each vehicle is connected to at most one charger at a time."""returnModelConstraint(constraint=self.variables.assignment.sum(CHARGER)<=1,name="charger_one_charger_per_ev_constraint",)@constraintdef_get_charger_no_assignment_while_driving_constraint(self)->ModelConstraint:"""A driving vehicle cannot be assigned to a charger."""returnModelConstraint(constraint=self.variables.assignment<=1-self.electric_vehicles.trips.is_driving,name="charger_no_assignment_while_driving_constraint",)@constraintdef_get_charger_power_limit_constraint(self)->ModelConstraint:"""A vehicle's charging and discharging power is capped by its assigned charger's `max_power`. With no charger assigned the cap is 0, so a vehicle charges or discharges (V2G) only through a charger. """available_power=(self.variables.assignment*self.arrays.max_power).sum(CHARGER)vehicles=self.electric_vehicles.variablesreturnModelConstraint(constraint=vehicles.power_in+vehicles.power_out<=available_power,name="charger_power_limit_constraint",)defpower_injection(self)->None:"""Return None: chargers are outside the power balance; the vehicles they serve inject."""defdispatch(self,solution:xr.Dataset)->ChargerDispatch:"""Return the assignment of vehicles to chargers and the power each charger delivers."""assignment=solution[self.assignment_name]power:xr.DataArray=(assignment*solution[ElectricVehicleFormulation.power_in_name]).sum(EV)returnChargerDispatch(xr.Dataset({"assignment":assignment,"power":power}),CHARGER)
The formulation of the vehicles the chargers serve.
required
Source code in src/odys/optimization/formulations/charging.py
45464748495051525354555657585960
def__init__(self,chargers:Sequence[Charger],context:ModelContext,electric_vehicles:ElectricVehicleFormulation,)->None:"""Initialize with the chargers of the system and the electric vehicles they serve. Args: chargers: The chargers, at least one. context: The shared indexing of the problem. electric_vehicles: The formulation of the vehicles the chargers serve. """super().__init__(chargers,context)self.arrays=vectorize(ChargerArrays,chargers,self.coordinates)self.electric_vehicles=electric_vehicles
If there are chargers but no electric vehicle formulation was built before.
Source code in src/odys/optimization/formulations/charging.py
626364656667686970717273747576
@classmethoddefbuild(cls,inputs:FormulationInputs)->Self|None:"""Return the formulation of the chargers, or None if there are none. Raises: OdysError: If there are chargers but no electric vehicle formulation was built before. """chargers=inputs.of_type(Charger)ifnotchargers:returnNoneelectric_vehicles=inputs.formulation_of(ElectricVehicleFormulation)ifelectric_vehiclesisNone:msg="ChargingFormulation must be built after an ElectricVehicleFormulation."raiseOdysError(msg)returncls(chargers,inputs.context,electric_vehicles)
Return the assignment of vehicles to chargers and the power each charger delivers.
Source code in src/odys/optimization/formulations/charging.py
125126127128129
defdispatch(self,solution:xr.Dataset)->ChargerDispatch:"""Return the assignment of vehicles to chargers and the power each charger delivers."""assignment=solution[self.assignment_name]power:xr.DataArray=(assignment*solution[ElectricVehicleFormulation.power_in_name]).sum(EV)returnChargerDispatch(xr.Dataset({"assignment":assignment,"power":power}),CHARGER)
Source code in src/odys/optimization/formulations/charging.py
252627282930
classChargingVariables(BaseModel):"""Decision variables of the chargers."""model_config=ConfigDict(frozen=True,extra="forbid",arbitrary_types_allowed=True)assignment:linopy.Variable