1 mod analytics; 2 mod cap; 3 mod events; 4 mod make; 5 mod mechanics; 6 mod pandemic; 7 mod render; 8 mod router; 9 mod scheduler; 10 mod sim; 11 mod transit; 12 mod trips; 13 14 pub use self::analytics::{Analytics, TripPhase}; 15 pub(crate) use self::cap::CapSimState; 16 pub(crate) use self::events::Event; 17 pub use self::events::{AlertLocation, TripPhaseType}; 18 pub use self::make::{ 19 BorderSpawnOverTime, IndividTrip, OffMapLocation, OriginDestination, PersonSpec, Scenario, 20 ScenarioGenerator, ScenarioModifier, SimFlags, SpawnOverTime, SpawnTrip, TripSpawner, TripSpec, 21 }; 22 pub(crate) use self::mechanics::{ 23 DrivingSimState, IntersectionSimState, ParkingSimState, WalkingSimState, 24 }; 25 pub(crate) use self::pandemic::PandemicModel; 26 pub(crate) use self::router::{ActionAtEnd, Router}; 27 pub(crate) use self::scheduler::{Command, Scheduler}; 28 pub use self::sim::{AgentProperties, AlertHandler, Sim, SimCallback, SimOptions}; 29 pub(crate) use self::transit::TransitSimState; 30 pub use self::trips::{Person, PersonState, TripInfo, TripResult}; 31 pub use self::trips::{TripEndpoint, TripMode}; 32 pub(crate) use self::trips::{TripLeg, TripManager}; 33 pub use crate::render::{ 34 CarStatus, DontDrawAgents, DrawCarInput, DrawPedCrowdInput, DrawPedestrianInput, GetDrawAgents, 35 PedCrowdLocation, UnzoomedAgent, 36 }; 37 use abstutil::{deserialize_usize, serialize_usize}; 38 use geom::{Distance, Pt2D, Speed, Time}; 39 use map_model::{ 40 BuildingID, BusRouteID, BusStopID, DirectedRoadID, IntersectionID, LaneID, Map, ParkingLotID, 41 Path, PathConstraints, PathRequest, Position, 42 }; 43 use serde::{Deserialize, Serialize}; 44 use std::fmt; 45 46 // http://pccsc.net/bicycle-parking-info/ says 68 inches, which is 1.73m 47 pub const BIKE_LENGTH: Distance = Distance::const_meters(1.8); 48 // These two must be < PARKING_SPOT_LENGTH 49 pub const MIN_CAR_LENGTH: Distance = Distance::const_meters(4.5); 50 pub const MAX_CAR_LENGTH: Distance = Distance::const_meters(6.5); 51 // Note this is more than MAX_CAR_LENGTH 52 pub const BUS_LENGTH: Distance = Distance::const_meters(12.5); 53 pub const LIGHT_RAIL_LENGTH: Distance = Distance::const_meters(60.0); 54 55 // At all speeds (including at rest), cars must be at least this far apart, measured from front of 56 // one car to the back of the other. 57 pub const FOLLOWING_DISTANCE: Distance = Distance::const_meters(1.0); 58 59 // When spawning at borders, start the front of the vehicle this far along and gradually appear. 60 // Getting too close to EPSILON_DIST can lead to get_draw_car having no geometry at all. 61 pub const SPAWN_DIST: Distance = Distance::const_meters(0.05); 62 63 // The numeric ID must be globally unique, without considering VehicleType. VehicleType is bundled 64 // for convenient debugging. 65 // TODO Implement Eq, Hash, Ord manually to guarantee this. 66 #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] 67 pub struct CarID( 68 #[serde( 69 serialize_with = "serialize_usize", 70 deserialize_with = "deserialize_usize" 71 )] 72 pub usize, 73 pub VehicleType, 74 ); 75 76 impl fmt::Display for CarID { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result77 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 78 match self.1 { 79 VehicleType::Car => write!(f, "Car #{}", self.0), 80 VehicleType::Bus => write!(f, "Bus #{}", self.0), 81 VehicleType::Train => write!(f, "Train #{}", self.0), 82 VehicleType::Bike => write!(f, "Bike #{}", self.0), 83 } 84 } 85 } 86 87 #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] 88 pub struct PedestrianID( 89 #[serde( 90 serialize_with = "serialize_usize", 91 deserialize_with = "deserialize_usize" 92 )] 93 pub usize, 94 ); 95 96 impl fmt::Display for PedestrianID { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result97 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 98 write!(f, "Pedestrian #{}", self.0) 99 } 100 } 101 102 #[derive(Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug, Hash)] 103 pub enum AgentID { 104 Car(CarID), 105 Pedestrian(PedestrianID), 106 // TODO Rename... 107 BusPassenger(PersonID, CarID), 108 } 109 110 impl AgentID { as_car(self) -> CarID111 pub(crate) fn as_car(self) -> CarID { 112 match self { 113 AgentID::Car(id) => id, 114 _ => panic!("Not a CarID: {:?}", self), 115 } 116 } 117 to_type(self) -> AgentType118 pub fn to_type(self) -> AgentType { 119 match self { 120 AgentID::Car(c) => match c.1 { 121 VehicleType::Car => AgentType::Car, 122 VehicleType::Bike => AgentType::Bike, 123 VehicleType::Bus => AgentType::Bus, 124 VehicleType::Train => AgentType::Train, 125 }, 126 AgentID::Pedestrian(_) => AgentType::Pedestrian, 127 AgentID::BusPassenger(_, _) => AgentType::TransitRider, 128 } 129 } 130 } 131 132 impl fmt::Display for AgentID { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result133 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 134 match self { 135 AgentID::Car(id) => write!(f, "AgentID({})", id), 136 AgentID::Pedestrian(id) => write!(f, "AgentID({})", id), 137 AgentID::BusPassenger(person, bus) => write!(f, "AgentID({} on {})", person, bus), 138 } 139 } 140 } 141 142 #[derive(Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug, Hash)] 143 pub enum AgentType { 144 Car, 145 Bike, 146 Bus, 147 Train, 148 Pedestrian, 149 TransitRider, 150 } 151 152 impl AgentType { all() -> Vec<AgentType>153 pub fn all() -> Vec<AgentType> { 154 vec![ 155 AgentType::Car, 156 AgentType::Bike, 157 AgentType::Bus, 158 AgentType::Train, 159 AgentType::Pedestrian, 160 AgentType::TransitRider, 161 ] 162 } 163 noun(self) -> &'static str164 pub fn noun(self) -> &'static str { 165 match self { 166 AgentType::Car => "Car", 167 AgentType::Bike => "Bike", 168 AgentType::Bus => "Bus", 169 AgentType::Train => "Train", 170 AgentType::Pedestrian => "Pedestrian", 171 AgentType::TransitRider => "Transit rider", 172 } 173 } 174 ongoing_verb(self) -> &'static str175 pub fn ongoing_verb(self) -> &'static str { 176 match self { 177 AgentType::Car => "driving", 178 AgentType::Bike => "biking", 179 AgentType::Bus | AgentType::Train => unreachable!(), 180 AgentType::Pedestrian => "walking", 181 AgentType::TransitRider => "riding transit", 182 } 183 } 184 } 185 186 #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] 187 pub struct TripID( 188 #[serde( 189 serialize_with = "serialize_usize", 190 deserialize_with = "deserialize_usize" 191 )] 192 pub usize, 193 ); 194 195 impl fmt::Display for TripID { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result196 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 197 write!(f, "Trip #{}", self.0) 198 } 199 } 200 201 #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] 202 pub struct PersonID( 203 #[serde( 204 serialize_with = "serialize_usize", 205 deserialize_with = "deserialize_usize" 206 )] 207 pub usize, 208 ); 209 210 impl fmt::Display for PersonID { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result211 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 212 write!(f, "Person {}", self.0) 213 } 214 } 215 216 #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] 217 pub struct OrigPersonID( 218 #[serde( 219 serialize_with = "serialize_usize", 220 deserialize_with = "deserialize_usize" 221 )] 222 pub usize, 223 #[serde( 224 serialize_with = "serialize_usize", 225 deserialize_with = "deserialize_usize" 226 )] 227 pub usize, 228 ); 229 230 #[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)] 231 pub enum VehicleType { 232 Car, 233 Bus, 234 Train, 235 Bike, 236 } 237 238 impl fmt::Display for VehicleType { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result239 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 240 match self { 241 VehicleType::Car => write!(f, "car"), 242 VehicleType::Bus => write!(f, "bus"), 243 VehicleType::Train => write!(f, "train"), 244 VehicleType::Bike => write!(f, "bike"), 245 } 246 } 247 } 248 249 impl VehicleType { to_constraints(self) -> PathConstraints250 pub fn to_constraints(self) -> PathConstraints { 251 match self { 252 VehicleType::Car => PathConstraints::Car, 253 VehicleType::Bus => PathConstraints::Bus, 254 VehicleType::Train => PathConstraints::Train, 255 VehicleType::Bike => PathConstraints::Bike, 256 } 257 } 258 is_transit(self) -> bool259 pub(crate) fn is_transit(self) -> bool { 260 match self { 261 VehicleType::Car => false, 262 VehicleType::Bus => true, 263 VehicleType::Train => true, 264 VehicleType::Bike => false, 265 } 266 } 267 } 268 269 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] 270 pub struct Vehicle { 271 pub id: CarID, 272 pub owner: Option<PersonID>, 273 pub vehicle_type: VehicleType, 274 pub length: Distance, 275 pub max_speed: Option<Speed>, 276 } 277 278 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] 279 pub struct VehicleSpec { 280 pub vehicle_type: VehicleType, 281 pub length: Distance, 282 pub max_speed: Option<Speed>, 283 } 284 285 impl VehicleSpec { make(self, id: CarID, owner: Option<PersonID>) -> Vehicle286 pub fn make(self, id: CarID, owner: Option<PersonID>) -> Vehicle { 287 assert_eq!(id.1, self.vehicle_type); 288 Vehicle { 289 id, 290 owner, 291 vehicle_type: self.vehicle_type, 292 length: self.length, 293 max_speed: self.max_speed, 294 } 295 } 296 } 297 298 #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] 299 pub enum ParkingSpot { 300 // Lane and idx 301 Onstreet(LaneID, usize), 302 // Building and idx (pretty meaningless) 303 Offstreet(BuildingID, usize), 304 Lot(ParkingLotID, usize), 305 } 306 307 #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] 308 pub struct ParkedCar { 309 pub vehicle: Vehicle, 310 pub spot: ParkingSpot, 311 } 312 313 // It'd be nice to inline the goal_pos like SidewalkSpot does, but DrivingGoal is persisted in 314 // Scenarios, so this wouldn't survive map edits. 315 #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] 316 pub enum DrivingGoal { 317 ParkNear(BuildingID), 318 Border(IntersectionID, LaneID, Option<OffMapLocation>), 319 } 320 321 impl DrivingGoal { end_at_border( dr: DirectedRoadID, constraints: PathConstraints, destination: Option<OffMapLocation>, map: &Map, ) -> Option<DrivingGoal>322 pub fn end_at_border( 323 dr: DirectedRoadID, 324 constraints: PathConstraints, 325 destination: Option<OffMapLocation>, 326 map: &Map, 327 ) -> Option<DrivingGoal> { 328 let lanes = dr.lanes(constraints, map); 329 if lanes.is_empty() { 330 None 331 } else { 332 // TODO ideally could use any 333 Some(DrivingGoal::Border(dr.dst_i(map), lanes[0], destination)) 334 } 335 } 336 goal_pos(&self, constraints: PathConstraints, map: &Map) -> Option<Position>337 pub fn goal_pos(&self, constraints: PathConstraints, map: &Map) -> Option<Position> { 338 match self { 339 DrivingGoal::ParkNear(b) => match constraints { 340 PathConstraints::Car => { 341 Some(Position::start(map.find_driving_lane_near_building(*b))) 342 } 343 PathConstraints::Bike => Some(map.get_b(*b).biking_connection(map)?.0), 344 PathConstraints::Bus | PathConstraints::Train | PathConstraints::Pedestrian => { 345 unreachable!() 346 } 347 }, 348 DrivingGoal::Border(_, l, _) => Some(Position::end(*l, map)), 349 } 350 } 351 make_router(&self, owner: CarID, path: Path, map: &Map) -> Router352 pub(crate) fn make_router(&self, owner: CarID, path: Path, map: &Map) -> Router { 353 match self { 354 DrivingGoal::ParkNear(b) => { 355 if owner.1 == VehicleType::Bike { 356 Router::bike_then_stop(owner, path, SidewalkSpot::bike_rack(*b, map).unwrap()) 357 } else { 358 Router::park_near(owner, path, *b) 359 } 360 } 361 DrivingGoal::Border(i, last_lane, _) => { 362 Router::end_at_border(owner, path, map.get_l(*last_lane).length(), *i) 363 } 364 } 365 } 366 pt(&self, map: &Map) -> Pt2D367 pub fn pt(&self, map: &Map) -> Pt2D { 368 match self { 369 DrivingGoal::ParkNear(b) => map.get_b(*b).polygon.center(), 370 DrivingGoal::Border(i, _, _) => map.get_i(*i).polygon.center(), 371 } 372 } 373 } 374 375 #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] 376 pub struct SidewalkSpot { 377 pub connection: SidewalkPOI, 378 pub sidewalk_pos: Position, 379 } 380 381 // Point of interest, that is 382 #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] 383 pub enum SidewalkPOI { 384 // Note that for offstreet parking, the path will be the same as the building's front path. 385 ParkingSpot(ParkingSpot), 386 // Don't actually know where this goes yet! 387 DeferredParkingSpot, 388 Building(BuildingID), 389 BusStop(BusStopID), 390 Border(IntersectionID, Option<OffMapLocation>), 391 // The bikeable position 392 BikeRack(Position), 393 SuddenlyAppear, 394 } 395 396 impl SidewalkSpot { 397 // Pretty hacky case deferred_parking_spot() -> SidewalkSpot398 pub fn deferred_parking_spot() -> SidewalkSpot { 399 SidewalkSpot { 400 connection: SidewalkPOI::DeferredParkingSpot, 401 // Dummy value 402 sidewalk_pos: Position::start(LaneID(0)), 403 } 404 } 405 parking_spot( spot: ParkingSpot, map: &Map, parking_sim: &ParkingSimState, ) -> SidewalkSpot406 pub fn parking_spot( 407 spot: ParkingSpot, 408 map: &Map, 409 parking_sim: &ParkingSimState, 410 ) -> SidewalkSpot { 411 SidewalkSpot { 412 connection: SidewalkPOI::ParkingSpot(spot), 413 sidewalk_pos: parking_sim.spot_to_sidewalk_pos(spot, map), 414 } 415 } 416 building(b: BuildingID, map: &Map) -> SidewalkSpot417 pub fn building(b: BuildingID, map: &Map) -> SidewalkSpot { 418 SidewalkSpot { 419 connection: SidewalkPOI::Building(b), 420 sidewalk_pos: map.get_b(b).sidewalk_pos, 421 } 422 } 423 424 // TODO For the case when we have to start/stop biking somewhere else, this won't match up with 425 // a building though! bike_rack(b: BuildingID, map: &Map) -> Option<SidewalkSpot>426 pub fn bike_rack(b: BuildingID, map: &Map) -> Option<SidewalkSpot> { 427 let (bike_pos, sidewalk_pos) = map.get_b(b).biking_connection(map)?; 428 Some(SidewalkSpot { 429 connection: SidewalkPOI::BikeRack(bike_pos), 430 sidewalk_pos, 431 }) 432 } 433 bus_stop(stop: BusStopID, map: &Map) -> SidewalkSpot434 pub fn bus_stop(stop: BusStopID, map: &Map) -> SidewalkSpot { 435 SidewalkSpot { 436 sidewalk_pos: map.get_bs(stop).sidewalk_pos, 437 connection: SidewalkPOI::BusStop(stop), 438 } 439 } 440 441 // Recall sidewalks are bidirectional. start_at_border( i: IntersectionID, origin: Option<OffMapLocation>, map: &Map, ) -> Option<SidewalkSpot>442 pub fn start_at_border( 443 i: IntersectionID, 444 origin: Option<OffMapLocation>, 445 map: &Map, 446 ) -> Option<SidewalkSpot> { 447 let lanes = map 448 .get_i(i) 449 .get_outgoing_lanes(map, PathConstraints::Pedestrian); 450 if !lanes.is_empty() { 451 return Some(SidewalkSpot { 452 sidewalk_pos: Position::start(lanes[0]), 453 connection: SidewalkPOI::Border(i, origin), 454 }); 455 } 456 457 map.get_i(i) 458 .get_incoming_lanes(map, PathConstraints::Pedestrian) 459 .next() 460 .map(|l| SidewalkSpot { 461 sidewalk_pos: Position::end(l, map), 462 connection: SidewalkPOI::Border(i, origin), 463 }) 464 } 465 end_at_border( i: IntersectionID, destination: Option<OffMapLocation>, map: &Map, ) -> Option<SidewalkSpot>466 pub fn end_at_border( 467 i: IntersectionID, 468 destination: Option<OffMapLocation>, 469 map: &Map, 470 ) -> Option<SidewalkSpot> { 471 if let Some(l) = map 472 .get_i(i) 473 .get_incoming_lanes(map, PathConstraints::Pedestrian) 474 .next() 475 { 476 return Some(SidewalkSpot { 477 sidewalk_pos: Position::end(l, map), 478 connection: SidewalkPOI::Border(i, destination), 479 }); 480 } 481 482 let lanes = map 483 .get_i(i) 484 .get_outgoing_lanes(map, PathConstraints::Pedestrian); 485 if lanes.is_empty() { 486 return None; 487 } 488 Some(SidewalkSpot { 489 sidewalk_pos: Position::start(lanes[0]), 490 connection: SidewalkPOI::Border(i, destination), 491 }) 492 } 493 suddenly_appear(l: LaneID, dist: Distance, map: &Map) -> SidewalkSpot494 pub fn suddenly_appear(l: LaneID, dist: Distance, map: &Map) -> SidewalkSpot { 495 let lane = map.get_l(l); 496 assert!(lane.is_walkable()); 497 assert!(dist <= lane.length()); 498 SidewalkSpot { 499 sidewalk_pos: Position::new(l, dist), 500 connection: SidewalkPOI::SuddenlyAppear, 501 } 502 } 503 } 504 505 #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] 506 pub struct TimeInterval { 507 // TODO Private fields 508 pub start: Time, 509 pub end: Time, 510 } 511 512 impl TimeInterval { new(start: Time, end: Time) -> TimeInterval513 pub fn new(start: Time, end: Time) -> TimeInterval { 514 if end < start { 515 panic!("Bad TimeInterval {} .. {}", start, end); 516 } 517 TimeInterval { start, end } 518 } 519 percent(&self, t: Time) -> f64520 pub fn percent(&self, t: Time) -> f64 { 521 if self.start == self.end { 522 return 1.0; 523 } 524 525 let x = (t - self.start) / (self.end - self.start); 526 assert!(x >= 0.0 && x <= 1.0); 527 x 528 } 529 percent_clamp_end(&self, t: Time) -> f64530 pub fn percent_clamp_end(&self, t: Time) -> f64 { 531 if t > self.end { 532 return 1.0; 533 } 534 self.percent(t) 535 } 536 } 537 538 #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] 539 pub struct DistanceInterval { 540 // TODO Private fields 541 pub start: Distance, 542 pub end: Distance, 543 } 544 545 impl DistanceInterval { new_driving(start: Distance, end: Distance) -> DistanceInterval546 pub fn new_driving(start: Distance, end: Distance) -> DistanceInterval { 547 if end < start { 548 panic!("Bad DistanceInterval {} .. {}", start, end); 549 } 550 DistanceInterval { start, end } 551 } 552 new_walking(start: Distance, end: Distance) -> DistanceInterval553 pub fn new_walking(start: Distance, end: Distance) -> DistanceInterval { 554 // start > end is fine, might be contraflow. 555 DistanceInterval { start, end } 556 } 557 lerp(&self, x: f64) -> Distance558 pub fn lerp(&self, x: f64) -> Distance { 559 assert!(x >= 0.0 && x <= 1.0); 560 self.start + x * (self.end - self.start) 561 } 562 length(&self) -> Distance563 pub fn length(&self) -> Distance { 564 (self.end - self.start).abs() 565 } 566 } 567 568 #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] 569 pub struct CreatePedestrian { 570 pub id: PedestrianID, 571 pub start: SidewalkSpot, 572 pub speed: Speed, 573 pub goal: SidewalkSpot, 574 pub req: PathRequest, 575 pub path: Path, 576 pub trip: TripID, 577 pub person: PersonID, 578 } 579 580 #[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] 581 pub struct CreateCar { 582 pub vehicle: Vehicle, 583 pub router: Router, 584 pub req: PathRequest, 585 pub start_dist: Distance, 586 pub maybe_parked_car: Option<ParkedCar>, 587 // None for buses 588 pub trip_and_person: Option<(TripID, PersonID)>, 589 pub maybe_route: Option<BusRouteID>, 590 } 591 592 impl CreateCar { for_appearing( vehicle: Vehicle, start_pos: Position, router: Router, req: PathRequest, trip: TripID, person: PersonID, ) -> CreateCar593 pub fn for_appearing( 594 vehicle: Vehicle, 595 start_pos: Position, 596 router: Router, 597 req: PathRequest, 598 trip: TripID, 599 person: PersonID, 600 ) -> CreateCar { 601 CreateCar { 602 vehicle, 603 router, 604 req, 605 start_dist: start_pos.dist_along(), 606 maybe_parked_car: None, 607 trip_and_person: Some((trip, person)), 608 maybe_route: None, 609 } 610 } 611 612 // TODO Maybe inline in trips, the only caller. for_parked_car( parked_car: ParkedCar, router: Router, req: PathRequest, start_dist: Distance, trip: TripID, person: PersonID, ) -> CreateCar613 pub fn for_parked_car( 614 parked_car: ParkedCar, 615 router: Router, 616 req: PathRequest, 617 start_dist: Distance, 618 trip: TripID, 619 person: PersonID, 620 ) -> CreateCar { 621 CreateCar { 622 vehicle: parked_car.vehicle.clone(), 623 router, 624 req, 625 start_dist, 626 maybe_parked_car: Some(parked_car), 627 trip_and_person: Some((trip, person)), 628 maybe_route: None, 629 } 630 } 631 } 632