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

core, editoast, python: stop train on next signal instead of OP #10200

Draft
wants to merge 2 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class PathfindingBlockRequest(
val rollingStockSupportedSignalingSystems: List<String>,
@Json(name = "rolling_stock_maximum_speed") val rollingStockMaximumSpeed: Double,
@Json(name = "rolling_stock_length") val rollingStockLength: Double,
@Json(name = "stop_at_next_signal") val stopAtNextSignal: Boolean,
val timeout: Double?,
val infra: String,
@Json(name = "expected_version") val expectedVersion: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,17 @@ fun runPathfinding(
): PathfindingBlockResponse {
// Parse the waypoints
val waypoints = ArrayList<Collection<PathfindingEdgeLocationId<Block>>>()
for (step in request.pathItems) {
request.pathItems.forEachIndexed { stepIndex, step ->
val allStarts = HashSet<PathfindingEdgeLocationId<Block>>()
for (direction in Direction.entries) {
for (waypoint in step) allStarts.addAll(findWaypointBlocks(infra, waypoint, direction))
for (waypoint in step) {
val waypointBlocks = findWaypointBlocks(infra, waypoint, direction)
if (request.stopAtNextSignal && stepIndex != 0) {
allStarts.addAll(waypointBlocks.map { findWaypointBlockNextSignalBlock(request, it, infra, stepIndex) })
} else {
allStarts.addAll(waypointBlocks)
}
}
}
waypoints.add(allStarts)
}
Expand Down Expand Up @@ -369,3 +376,32 @@ private fun getBlockOffset(
String.format("getBlockOffset: Track chunk %s not in block %s", trackChunkId, blockId)
)
}

private fun findWaypointBlockNextSignalBlock(
request: PathfindingBlockRequest,
waypointBlock: PathfindingEdgeLocationId<Block>,
infra: FullInfra,
index: Int,
): PathfindingEdgeLocationId<Block> {
val nextSignalOffset = getNextSignalOffset(waypointBlock.edge, waypointBlock.offset, infra, request.rollingStockLength, index)
return PathfindingEdgeLocationId(waypointBlock.edge, nextSignalOffset)
}

private fun getNextSignalOffset(blockId: BlockId, blockOffset: Offset<Block>, infra: FullInfra, rollingStockLength: Double, index: Int): Offset<Block> {
val signalsPositions = infra.blockInfra.getSignalsPositions(blockId)
val blockLength = infra.blockInfra.getBlockLength(blockId).distance
val nextSignalPosition = signalsPositions.firstOrNull { it.distance >= blockOffset.distance }

// some blocks are < 1m long (even 0m), we can't get further in the block
val maxHeadOffset = if (blockOffset.distance < 1.meters) {
blockOffset.distance
} else {
(nextSignalPosition?.distance ?: blockLength) - 1.meters
}

val minTailOffset = blockOffset.distance + rollingStockLength.meters
val finalOffset = if (minTailOffset <= maxHeadOffset) minTailOffset else maxHeadOffset
// println("index: $index, signalsPositions: $signalsPositions, blockLength: $blockLength, blockOffset: $blockOffset, nextSignalPosition: $nextSignalPosition, finalOffset: $finalOffset")

return Offset(finalOffset)
}
Original file line number Diff line number Diff line change
Expand Up @@ -141,5 +141,6 @@ class SimulationPowerRestrictionItem(
)

class TrainScheduleOptions(
@Json(name = "use_electrical_profiles") val useElectricalProfiles: Boolean
@Json(name = "use_electrical_profiles") val useElectricalProfiles: Boolean,
// @Json(name = "stop_at_next_signal") val stopAtNextSignal: Boolean // TODO: not sure it is used still
)
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ private fun parseSteps(
return pathItems
.map {
STDCMStep(
findWaypointBlocks(infra, it.locations),
findWaypointBlocks(infra, it.locations), // TODO: use moveWaypointBlockToNextSignal()
it.stopDuration?.seconds,
it.stopDuration != null,
if (it.stepTimingData != null)
Expand Down Expand Up @@ -362,7 +362,7 @@ private fun checkForConflicts(

private fun findWaypointBlocks(
infra: FullInfra,
waypoints: Collection<TrackLocation>
waypoints: Collection<TrackLocation>,
): Set<PathfindingEdgeLocationId<Block>> {
val waypointBlocks = HashSet<PathfindingEdgeLocationId<Block>>()
for (waypoint in waypoints) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class STDCMRequestV2(
@Json(name = "rolling_stock_supported_signaling_systems")
val rollingStockSupportedSignalingSystems: List<String>,
@Json(name = "trains_requirements") val trainsRequirements: Map<Long, TrainRequirementsRequest>,
@Json(name = "stop_at_next_signal") val stopAtNextSignal: Boolean,

// Simulation inputs
val comfort: Comfort,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,22 @@ pub struct TrainScheduleOptions {
#[derivative(Default(value = "true"))]
#[serde(default = "default_use_electrical_profiles")]
use_electrical_profiles: bool,

#[derivative(Default(value = "true"))]
#[serde(default = "default_stop_at_next_signal")] // TODO: try to set default value only at 1 location (struct)
stop_at_next_signal: bool,
}

fn default_use_electrical_profiles() -> bool {
true
}

fn default_stop_at_next_signal() -> bool {
true
}

impl TrainScheduleOptions {
pub fn stops_at_next_signal(&self) -> bool {
self.stop_at_next_signal
}
}
7 changes: 7 additions & 0 deletions editoast/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8160,6 +8160,9 @@ components:
items:
type: string
description: List of supported signaling systems
stop_at_next_signal:
type: boolean
description: Stops the train at next signal instead of on path item
PathfindingInputError:
oneOf:
- type: object
Expand Down Expand Up @@ -11346,6 +11349,8 @@ components:
options:
type: object
properties:
stop_at_next_signal:
type: boolean
use_electrical_profiles:
type: boolean
additionalProperties: false
Expand Down Expand Up @@ -11441,6 +11446,8 @@ components:
TrainScheduleOptions:
type: object
properties:
stop_at_next_signal:
type: boolean
use_electrical_profiles:
type: boolean
additionalProperties: false
Expand Down
3 changes: 3 additions & 0 deletions editoast/src/core/pathfinding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ pub struct PathfindingRequest {
pub rolling_stock_maximum_speed: f64,
/// Rolling stock length in meters:
pub rolling_stock_length: f64,
/// If the train should stop on the next signal instead of on the operational point
// TODO: test if this field is really used
pub stop_at_next_signal: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ToSchema)]
Expand Down
10 changes: 10 additions & 0 deletions editoast/src/views/path/pathfinding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,14 @@ struct PathfindingInput {
/// Rolling stock length
#[schema(value_type = f64)]
rolling_stock_length: OrderedFloat<f64>,
/// Stops the train at next signal instead of on path item
// TODO: try to set default value only at 1 location (struct)
#[serde(default = "default_stop_at_next_signal")]
stop_at_next_signal: bool,
}

fn default_stop_at_next_signal() -> bool {
true
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, ToSchema)]
Expand Down Expand Up @@ -325,6 +333,7 @@ fn build_pathfinding_request(
.clone(),
rolling_stock_maximum_speed: pathfinding_input.rolling_stock_maximum_speed.0,
rolling_stock_length: pathfinding_input.rolling_stock_length.0,
stop_at_next_signal: pathfinding_input.stop_at_next_signal,
})
}

Expand Down Expand Up @@ -398,6 +407,7 @@ pub async fn pathfinding_from_train_batch(
.into_iter()
.map(|item| item.location)
.collect(),
stop_at_next_signal: train_schedule.options.stops_at_next_signal(),
};
to_compute.push(path_input);
to_compute_index.push(index);
Expand Down
9 changes: 8 additions & 1 deletion python/osrd_schemas/osrd_schemas/train_schedule.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,12 +170,19 @@ class PowerRestrictionRanges(RootModel):


class TrainScheduleOptions(BaseModel):
"""Optional arguments for the standalone simulation."""
"""Optional arguments :
- `ignore_electrical_profiles` : for the standalone simulation
- `stop_at_next_signal` : for dealing with stopped trains that overflow on switches during imports
"""

ignore_electrical_profiles: bool = Field(
default=False,
description="If true, the electrical profiles are ignored in the standalone simulation",
)
stop_at_next_signal: bool = Field(
default=False,
description="If true, the train will stop at the next signal instead of at the operational point",
)


if __name__ == "__main__":
Expand Down
Loading