304
社区成员
发帖
与我相关
我的任务
分享在三次作业中,我对线程间的通信采用了生产者-消费者模型,生产者-消费者模型中同步块和锁通常设置在对队列的临界操作中,我只需要保证队列同时只有一个线程访问,即可保证线程不会出现交叉运行的错误。在生产者-消费者模型中,通常生产者-消费者内部的代码运行时间较长,因此对队列设置严格的锁对多线程程序的效率影响不大。
同时,我在双轿厢的设计中,使用了类似读写锁的分权限/优先级的锁结构,来维护双轿厢不会同时处于 F2 中。为什么采用这种独特的锁呢?因为我设计的是当某一轿厢想要获取 F2 时,他将检查状态,若 F2 处于强锁状态,那么就等待,若处于弱锁状态,那么就驱逐,如果无锁,那么就获锁。这种结构允许我能够临时驱逐 F2。
我对锁和同步块的理解就是,一个资源如果与时序强相关或者说我需要原子化的操作,我就需要获锁来保证当前操作对该资源是原子的。
这个调度器设计真是这次作业的核心科技了(
在第一次作业中,我就初步判断,对于底层的单个电梯,他很难感知到全局状态而作出很多优化,如果我需要让他感知全局状态,我就需要改为由一个统一管理 6 个电梯的管理器向电梯投放单步操作,如:开门、关门、上升、下降。这种结构是相当不优雅且难扩展难维护的。因此我放弃了这部分优化(踢人优化)仅仅设置单个电梯执行 look 算法,然后 dispatcher 撑起优化的大梁。这种结构的好处就是会将决策简化为将乘客分配给哪个电梯。于是,白箱的极限,影子电梯应运而生。
第一次作业不涉及 dispatcher
第二次作业中,我尝试了影子电梯,并取得较好的成果。影子电梯就是为每个电梯提供结构使得可以计算当前分配的情况下,一直到停止运行耗费的代价。那么我们 dispatch 就变成了 “模拟然后比大小” 如此简单的过程。
第三次作业中,我尝试了调参,然后付出了惨痛的代价(因为测出来代码退化之后来不及提交原来的代码了)
正如上面所说,影子电梯就是贪心的极限,他直接以性能值为优化目标,所以可以很好适应时间、电量等多种性能指标。
对于这类问题我们可以使用 AI 辅助静态检查或者生成随机数据测试。
wait notify 而非 while () { check() }很多多线程相关的 bug 并不是稳定触发的:如原子性问题;这些 bug 既不好动态调试定位,也不好静态调试观察。
我们通过压测来定位 bug,并保留出现 bug 时的上下文状态。
在通过压测找到 bug 位置或出现原因等相关信息后,调试将会简单很多。
我们可以通过读取堆栈、变量状态,bug 附近代码,来进行静态调试,此时也可以很方便的使用 ai 辅助调试。
本次会议讨论了常见的 bug 原因和解决方案,以及多线程调试等问题。
在多线程程序中,通常寻找 bug 和调试的工作会较为麻烦,bug的种类繁多。
本次作业中常出现的bug围绕:边界细节,线程协同,锁,以及 dispacher。
多线程调试的技巧是压测和获取出现bug时的状态。
大模型太好用了你们知道吗(
本次主要使用了 codex-gpt-5.4
其实主要是让 AI 帮忙完善了一下影子电梯的 Simulator,其他的代码方面实在是不敢让 AI 帮忙,一个是会写出很奇怪的调度策略,另一个是 AI 在多线程问题上,如果你让他放飞自我的话,他会直接写出死锁,还有一点就是,AI 真的超级喜欢写轮询,这太坏了!
另外在互测的时候 AI 真的是帮了大忙,首先让 AI 光速写了个 spj 程序,然后又让 AI 写了包括数据生成器在内的全套评测机。帮我在 HW3 大杀四方。而且主要是省时间,感觉现在六系的课业负担实在太重,把需求扔给 AI 然后让他自己跑还是相当省心。
总的来说,AI 在这次作业中体现到的就是,他有很多莫名其妙的小巧思,然后就给自己写出问题了,而且很难精确的描述需求。
面向对象作业负担太重了太重了太重了太重了
首先性能分的设计让内卷永无止境,我认为应当适当放宽满分的设置,尤其是在这种电梯调度类似最优化的题目上,总有神人的灵机一动虽然过不了很多数据,但是能对某个测试点特攻。然后评测 CD 实在是小巧思拉满,现在评测机跑的也不慢,我说要不放宽评测冷却或者干脆直接去掉,这让我安排作业的时间会相当受限制。毕竟都已经有了提交次数限制。
最最最最神中神的一点是!!!!互测 CD 整整 30 min,在仅仅考虑拿 base 分的情况下,A 房同学需要花费宝贵的 2.5h 守在电脑前面然后连着提交,而 C 房同学来到了惊人的 5h,大家是不用上课天天守着电脑光想着卡点提交吗。如果我想要好好叉人那更是灾难,本人亲身体验 Unit1 HW3 想叉人,光启动测试就花了够长的时间了,测出来的5,6个 bug 一直给我叉到凌晨,到了 Unit2 更是变态,多线程 bug 不稳定触发 + 超长冷却,你就叉吧,我 Unit2 HW2 HW3 连续两次每次都是整整占我一整天,冷却中间的 30min 请问我拿来干什么呢,写别的作业时间又太短,很容易卡不上点提交bug,然后这 30min 还特别容易忘掉叉过哪些bug。现实情况就是,我在本地测到了3,4个多线程的bug,硬等 等两三个小时,然后交上去发现一个也叉不到,这集神了。
中测强度太低了!!!
再回到这次作业本身,电梯调度,但是大家卷出了踢人、量子电梯、一直开门等人等离谱的不贴合实际的优化。我认为还需要增加更多的限制来使得题目能够更加贴近现实,如:若本层在 0.4s 内没有继续上人就必须关门,电梯想要移动之前必须提前输出启动信息等等。同时,这种更加强的限制更能使得内卷程度下降。
总的来说,这个调度/优化这个课题虽然不在我的优势上,但是能够写一个这样的作业得到锻炼还是很不错的。
附上我的spj吧,其实数据投喂器我逆了一个并且完善了一下(但是我还没来得及看时间倒流这个神秘bug)(其实python打包的程序随便逆,ctf 里已经做过很多了),但是我有点懒得贴上来数据投喂器,我自己写的有点丑了
golang 是这个世界上最好的语言!
package main
import (
"bufio"
"errors"
"flag"
"fmt"
"os"
"sort"
"strconv"
"strings"
)
const (
tick = 10000
moveNormal = 4000
moveTest = 2000
doorGap = 4000
stopGap = 10000
maintLimit = 70000
updateLimit = 60000
recycleLimit = 60000
maxLoad = 400
)
type mode int
const (
modeNormal mode = iota
modeRepAccept
modeRepair
modeTest
modeUpAccept
modeUpdate
modeDouble
modeRecAccept
modeRecycle
)
type floor int
const (
b4 floor = iota
b3
b2
b1
f1
f2
f3
f4
f5
f6
f7
)
var floorNames = []string{
"B4", "B3", "B2", "B1", "F1", "F2", "F3", "F4", "F5", "F6", "F7",
}
var floorIndex = map[string]floor{
"B4": b4, "B3": b3, "B2": b2, "B1": b1,
"F1": f1, "F2": f2, "F3": f3, "F4": f4, "F5": f5, "F6": f6, "F7": f7,
}
type passengerReq struct {
ID int
Weight int
From floor
To floor
Time int
}
type maintReq struct {
ElevatorID int
WorkerID int
Target floor
Time int
}
type updateReq struct {
ElevatorID int
Time int
}
type recycleReq struct {
BackupID int
Time int
}
type caseData struct {
Passengers map[int]*passengerReq
PassengerIDs []int
MaintsByElevator map[int][]maintReq
UpdatesByElevator map[int][]updateReq
RecyclesByBackup map[int][]recycleReq
}
type passengerState struct {
AssignedCar int
InsideCar int
OutsideAt floor
Finished bool
FinalTime int
}
type workerState struct {
InCar int
OutsideAt floor
Finished bool
}
type maintRun struct {
AcceptAt int
BeginAt int
WorkerID int
Target floor
Phase string
}
type updateRun struct {
AcceptAt int
BeginAt int
}
type recycleRun struct {
AcceptAt int
BeginAt int
}
type timeValue struct {
Time int
Value int
}
type modeValue struct {
Time int
Value mode
}
type carState struct {
ID int
Shaft int
Main bool
Active bool
Floor floor
DoorOpen bool
OrdinaryLoad int
OrdinaryIDs map[int]struct{}
WorkerIDs map[int]struct{}
LastPhysicalTime int
LastOpenTime int
ReceiveCount int
ReceiveHistory []timeValue
}
type shaftState struct {
ID int
Mode mode
ModeHistory []modeValue
Maint *maintRun
Update *updateRun
Recycle *recycleRun
}
type metrics struct {
Trun float64
Tavg float64
W float64
}
type event struct {
Time int
Raw string
Kind string
SubKind string
PersonID int
Elevator int
WorkerID int
Floor floor
Target floor
}
type judge struct {
data *caseData
passengers map[int]*passengerState
workers map[int]*workerState
cars map[int]*carState
shafts map[int]*shaftState
maintIndex map[int]int
updateIndex map[int]int
recycleIndex map[int]int
lineNo int
lastOutputTime int
arriveCount int
openCount int
closeCount int
}
func main() {
inputPath := flag.String("input", "", "input file")
outputPath := flag.String("output", "", "output file")
realTime := flag.Float64("real", -1, "real time in seconds")
cpuTime := flag.Float64("cpu", -1, "cpu time in seconds")
tmax := flag.Float64("tmax", 180, "time limit in seconds")
flag.Parse()
if *inputPath == "" || *outputPath == "" {
failf("usage: go run spj.go -input case.txt -output out.txt [-real 12.3] [-cpu 0.8] [-tmax 180]")
}
data, err := parseInputFile(*inputPath)
if err != nil {
failf("input parse error: %v", err)
}
j := newJudge(data)
if err := j.judgeOutput(*outputPath); err != nil {
failf("WA: %v", err)
}
res, err := j.finish(*realTime, *cpuTime, *tmax)
if err != nil {
failf("WA: %v", err)
}
fmt.Println("OK")
fmt.Printf("Tfinal=%.4f\n", seconds(j.lastOutputTime))
if *realTime >= 0 {
fmt.Printf("Treal=%.4f\n", *realTime)
fmt.Printf("Trun=%.4f\n", res.Trun)
}
fmt.Printf("Tavg=%.4f\n", res.Tavg)
fmt.Printf("W=%.1f\n", res.W)
fmt.Printf("NARRIVE=%d\n", j.arriveCount)
fmt.Printf("NOPEN=%d\n", j.openCount)
fmt.Printf("NCLOSE=%d\n", j.closeCount)
if *cpuTime >= 0 {
fmt.Printf("CPU=%.4f\n", *cpuTime)
}
}
func parseInputFile(path string) (*caseData, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
data := &caseData{
Passengers: make(map[int]*passengerReq),
MaintsByElevator: make(map[int][]maintReq),
UpdatesByElevator: make(map[int][]updateReq),
RecyclesByBackup: make(map[int][]recycleReq),
}
scanner := bufio.NewScanner(file)
lineNo := 0
for scanner.Scan() {
lineNo++
line := strings.TrimSpace(scanner.Text())
if line == "" {
continue
}
if err := parseInputLine(data, line); err != nil {
return nil, fmt.Errorf("line %d: %w", lineNo, err)
}
}
if err := scanner.Err(); err != nil {
return nil, err
}
sort.Ints(data.PassengerIDs)
return data, nil
}
func parseInputLine(data *caseData, line string) error {
body, ts, err := splitTimestamp(line, 1)
if err != nil {
return err
}
switch {
case strings.Contains(body, "-WEI-"):
return parsePassengerInput(data, body, ts)
case strings.HasPrefix(body, "MAINT-"):
return parseMaintInput(data, body, ts)
case strings.HasPrefix(body, "UPDATE-"):
return parseUpdateInput(data, body, ts)
case strings.HasPrefix(body, "RECYCLE-"):
return parseRecycleInput(data, body, ts)
default:
return fmt.Errorf("unknown input event %q", body)
}
}
func parsePassengerInput(data *caseData, body string, ts int) error {
parts := strings.Split(body, "-")
if len(parts) != 7 || parts[1] != "WEI" || parts[3] != "FROM" || parts[5] != "TO" {
return fmt.Errorf("bad passenger input %q", body)
}
id, err := atoi(parts[0], "passenger id")
if err != nil {
return err
}
weight, err := atoi(parts[2], "weight")
if err != nil {
return err
}
from, err := parseFloor(parts[4])
if err != nil {
return err
}
to, err := parseFloor(parts[6])
if err != nil {
return err
}
if _, exists := data.Passengers[id]; exists {
return fmt.Errorf("duplicate passenger id %d", id)
}
data.Passengers[id] = &passengerReq{
ID: id, Weight: weight, From: from, To: to, Time: ts,
}
data.PassengerIDs = append(data.PassengerIDs, id)
return nil
}
func parseMaintInput(data *caseData, body string, ts int) error {
parts := strings.Split(body, "-")
if len(parts) != 4 {
return fmt.Errorf("bad MAINT input %q", body)
}
eid, err := atoi(parts[1], "elevator id")
if err != nil {
return err
}
wid, err := atoi(parts[2], "worker id")
if err != nil {
return err
}
target, err := parseFloor(parts[3])
if err != nil {
return err
}
data.MaintsByElevator[eid] = append(data.MaintsByElevator[eid], maintReq{
ElevatorID: eid, WorkerID: wid, Target: target, Time: ts,
})
return nil
}
func parseUpdateInput(data *caseData, body string, ts int) error {
parts := strings.Split(body, "-")
if len(parts) != 2 {
return fmt.Errorf("bad UPDATE input %q", body)
}
eid, err := atoi(parts[1], "elevator id")
if err != nil {
return err
}
data.UpdatesByElevator[eid] = append(data.UpdatesByElevator[eid], updateReq{
ElevatorID: eid, Time: ts,
})
return nil
}
func parseRecycleInput(data *caseData, body string, ts int) error {
parts := strings.Split(body, "-")
if len(parts) != 2 {
return fmt.Errorf("bad RECYCLE input %q", body)
}
eid, err := atoi(parts[1], "backup elevator id")
if err != nil {
return err
}
data.RecyclesByBackup[eid] = append(data.RecyclesByBackup[eid], recycleReq{
BackupID: eid, Time: ts,
})
return nil
}
func newJudge(data *caseData) *judge {
j := &judge{
data: data,
passengers: make(map[int]*passengerState),
workers: make(map[int]*workerState),
cars: make(map[int]*carState),
shafts: make(map[int]*shaftState),
maintIndex: make(map[int]int),
updateIndex: make(map[int]int),
recycleIndex: make(map[int]int),
}
for id, req := range data.Passengers {
j.passengers[id] = &passengerState{
OutsideAt: req.From,
}
}
for shaftID := 1; shaftID <= 6; shaftID++ {
j.shafts[shaftID] = &shaftState{
ID: shaftID,
Mode: modeNormal,
ModeHistory: []modeValue{{Time: 0, Value: modeNormal}},
}
j.cars[shaftID] = &carState{
ID: shaftID,
Shaft: shaftID,
Main: true,
Active: true,
Floor: f1,
OrdinaryIDs: make(map[int]struct{}),
WorkerIDs: make(map[int]struct{}),
ReceiveHistory: []timeValue{{Time: 0, Value: 0}},
}
j.cars[shaftID+6] = &carState{
ID: shaftID + 6,
Shaft: shaftID,
Main: false,
Active: false,
Floor: f1,
OrdinaryIDs: make(map[int]struct{}),
WorkerIDs: make(map[int]struct{}),
ReceiveHistory: []timeValue{{Time: 0, Value: 0}},
}
}
return j
}
func (j *judge) judgeOutput(path string) error {
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
j.lineNo++
line := strings.TrimSpace(scanner.Text())
if line == "" {
continue
}
ev, err := parseOutputLine(line)
if err != nil {
return fmt.Errorf("line %d: %w", j.lineNo, err)
}
if ev.Time < j.lastOutputTime {
return fmt.Errorf("line %d: timestamp decreases from %.4f to %.4f",
j.lineNo, seconds(j.lastOutputTime), seconds(ev.Time))
}
j.lastOutputTime = ev.Time
if err := j.handleEvent(ev); err != nil {
return fmt.Errorf("line %d: %w", j.lineNo, err)
}
}
if err := scanner.Err(); err != nil {
return err
}
return nil
}
func parseOutputLine(line string) (*event, error) {
body, ts, err := splitTimestamp(line, 4)
if err != nil {
return nil, err
}
ev := &event{Time: ts, Raw: body}
switch {
case strings.HasPrefix(body, "RECEIVE-"):
parts := strings.Split(body, "-")
if len(parts) != 3 {
return nil, fmt.Errorf("bad RECEIVE %q", body)
}
ev.Kind = "RECEIVE"
ev.PersonID, err = atoi(parts[1], "person id")
if err != nil {
return nil, err
}
ev.Elevator, err = atoi(parts[2], "elevator id")
if err != nil {
return nil, err
}
case strings.HasPrefix(body, "ARRIVE-"), strings.HasPrefix(body, "OPEN-"), strings.HasPrefix(body, "CLOSE-"):
parts := strings.Split(body, "-")
if len(parts) != 3 {
return nil, fmt.Errorf("bad event %q", body)
}
ev.Kind = parts[0]
ev.Floor, err = parseFloor(parts[1])
if err != nil {
return nil, err
}
ev.Elevator, err = atoi(parts[2], "elevator id")
if err != nil {
return nil, err
}
case strings.HasPrefix(body, "IN-"):
parts := strings.Split(body, "-")
if len(parts) != 4 {
return nil, fmt.Errorf("bad IN %q", body)
}
ev.Kind = "IN"
ev.PersonID, err = atoi(parts[1], "person id")
if err != nil {
return nil, err
}
ev.Floor, err = parseFloor(parts[2])
if err != nil {
return nil, err
}
ev.Elevator, err = atoi(parts[3], "elevator id")
if err != nil {
return nil, err
}
case strings.HasPrefix(body, "OUT-S-"), strings.HasPrefix(body, "OUT-F-"):
parts := strings.Split(body, "-")
if len(parts) != 5 {
return nil, fmt.Errorf("bad OUT %q", body)
}
ev.Kind = "OUT"
ev.SubKind = parts[1]
ev.PersonID, err = atoi(parts[2], "person id")
if err != nil {
return nil, err
}
ev.Floor, err = parseFloor(parts[3])
if err != nil {
return nil, err
}
ev.Elevator, err = atoi(parts[4], "elevator id")
if err != nil {
return nil, err
}
case strings.HasPrefix(body, "MAINT-ACCEPT-"):
parts := strings.Split(body, "-")
if len(parts) != 5 {
return nil, fmt.Errorf("bad MAINT-ACCEPT %q", body)
}
ev.Kind = "MAINT-ACCEPT"
ev.Elevator, err = atoi(parts[2], "elevator id")
if err != nil {
return nil, err
}
ev.WorkerID, err = atoi(parts[3], "worker id")
if err != nil {
return nil, err
}
ev.Target, err = parseFloor(parts[4])
if err != nil {
return nil, err
}
case strings.HasPrefix(body, "MAINT1-BEGIN-"):
ev.Kind = "MAINT1-BEGIN"
ev.Elevator, err = atoi(strings.TrimPrefix(body, "MAINT1-BEGIN-"), "elevator id")
if err != nil {
return nil, err
}
case strings.HasPrefix(body, "MAINT2-BEGIN-"):
ev.Kind = "MAINT2-BEGIN"
ev.Elevator, err = atoi(strings.TrimPrefix(body, "MAINT2-BEGIN-"), "elevator id")
if err != nil {
return nil, err
}
case strings.HasPrefix(body, "MAINT-END-"):
ev.Kind = "MAINT-END"
ev.Elevator, err = atoi(strings.TrimPrefix(body, "MAINT-END-"), "elevator id")
if err != nil {
return nil, err
}
case strings.HasPrefix(body, "UPDATE-ACCEPT-"):
ev.Kind = "UPDATE-ACCEPT"
ev.Elevator, err = atoi(strings.TrimPrefix(body, "UPDATE-ACCEPT-"), "elevator id")
if err != nil {
return nil, err
}
case strings.HasPrefix(body, "UPDATE-BEGIN-"):
ev.Kind = "UPDATE-BEGIN"
ev.Elevator, err = atoi(strings.TrimPrefix(body, "UPDATE-BEGIN-"), "elevator id")
if err != nil {
return nil, err
}
case strings.HasPrefix(body, "UPDATE-END-"):
ev.Kind = "UPDATE-END"
ev.Elevator, err = atoi(strings.TrimPrefix(body, "UPDATE-END-"), "elevator id")
if err != nil {
return nil, err
}
case strings.HasPrefix(body, "RECYCLE-ACCEPT-"):
ev.Kind = "RECYCLE-ACCEPT"
ev.Elevator, err = atoi(strings.TrimPrefix(body, "RECYCLE-ACCEPT-"), "backup elevator id")
if err != nil {
return nil, err
}
case strings.HasPrefix(body, "RECYCLE-BEGIN-"):
ev.Kind = "RECYCLE-BEGIN"
ev.Elevator, err = atoi(strings.TrimPrefix(body, "RECYCLE-BEGIN-"), "backup elevator id")
if err != nil {
return nil, err
}
case strings.HasPrefix(body, "RECYCLE-END-"):
ev.Kind = "RECYCLE-END"
ev.Elevator, err = atoi(strings.TrimPrefix(body, "RECYCLE-END-"), "backup elevator id")
if err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("unknown output event %q", body)
}
return ev, nil
}
func (j *judge) handleEvent(ev *event) error {
switch ev.Kind {
case "RECEIVE":
return j.handleReceive(ev)
case "ARRIVE":
return j.handleArrive(ev)
case "OPEN":
return j.handleOpen(ev)
case "CLOSE":
return j.handleClose(ev)
case "IN":
return j.handleIn(ev)
case "OUT":
return j.handleOut(ev)
case "MAINT-ACCEPT":
return j.handleMaintAccept(ev)
case "MAINT1-BEGIN":
return j.handleMaint1Begin(ev)
case "MAINT2-BEGIN":
return j.handleMaint2Begin(ev)
case "MAINT-END":
return j.handleMaintEnd(ev)
case "UPDATE-ACCEPT":
return j.handleUpdateAccept(ev)
case "UPDATE-BEGIN":
return j.handleUpdateBegin(ev)
case "UPDATE-END":
return j.handleUpdateEnd(ev)
case "RECYCLE-ACCEPT":
return j.handleRecycleAccept(ev)
case "RECYCLE-BEGIN":
return j.handleRecycleBegin(ev)
case "RECYCLE-END":
return j.handleRecycleEnd(ev)
default:
return fmt.Errorf("unsupported event %s", ev.Kind)
}
}
func (j *judge) handleReceive(ev *event) error {
car, shaft, err := j.lookupCar(ev.Elevator)
if err != nil {
return err
}
req, ok := j.data.Passengers[ev.PersonID]
if !ok {
return fmt.Errorf("RECEIVE refers to unknown passenger %d", ev.PersonID)
}
ps := j.passengers[ev.PersonID]
if ps.Finished {
return fmt.Errorf("passenger %d already finished", ev.PersonID)
}
if ps.InsideCar != 0 {
return fmt.Errorf("passenger %d is still inside elevator %d", ev.PersonID, ps.InsideCar)
}
if ps.AssignedCar != 0 {
return fmt.Errorf("passenger %d already has unfinished RECEIVE on elevator %d", ev.PersonID, ps.AssignedCar)
}
if !carCanReceive(shaft.Mode, car.Main) {
return fmt.Errorf("elevator %d cannot RECEIVE in mode %s", car.ID, modeName(shaft.Mode))
}
if !car.Active {
return fmt.Errorf("elevator %d is inactive", car.ID)
}
if ps.OutsideAt == req.To {
return fmt.Errorf("passenger %d is already at final floor %s", ev.PersonID, floorName(req.To))
}
ps.AssignedCar = car.ID
car.ReceiveCount++
pushTimeValue(&car.ReceiveHistory, ev.Time, car.ReceiveCount)
return nil
}
func (j *judge) handleArrive(ev *event) error {
car, shaft, err := j.lookupCar(ev.Elevator)
if err != nil {
return err
}
if err := j.ensureCarActiveAndNotSilent(car, shaft, ev.Kind); err != nil {
return err
}
if err := j.ensureRepAcceptWorkerLock(car, shaft, ev.Kind); err != nil {
return err
}
if car.DoorOpen {
return fmt.Errorf("elevator %d moves with door open", car.ID)
}
prevFloor := car.Floor
if absInt(int(ev.Floor-prevFloor)) != 1 {
return fmt.Errorf("elevator %d moves from %s to %s without exactly one-floor ARRIVE",
car.ID, floorName(prevFloor), floorName(ev.Floor))
}
speed := j.moveDuration(car, shaft)
start := ev.Time - speed
if start < car.LastPhysicalTime {
return fmt.Errorf("elevator %d violates move timing: move starts at %.4f before previous physical action at %.4f",
car.ID, seconds(start), seconds(car.LastPhysicalTime))
}
if !j.moveAllowedAtStart(car, prevFloor, ev.Floor, start) {
return fmt.Errorf("elevator %d starts moving toward %s at %.4f without active RECEIVE or valid command",
car.ID, floorName(ev.Floor), seconds(start))
}
car.Floor = ev.Floor
car.LastPhysicalTime = ev.Time
j.arriveCount++
if err := j.validateArriveByMode(car, shaft, prevFloor, ev.Floor); err != nil {
return err
}
return j.checkShaftOrdering(car.Shaft)
}
func (j *judge) handleOpen(ev *event) error {
car, shaft, err := j.lookupCar(ev.Elevator)
if err != nil {
return err
}
if err := j.ensureCarActiveAndNotSilent(car, shaft, ev.Kind); err != nil {
return err
}
if err := j.ensureRepAcceptWorkerLock(car, shaft, ev.Kind); err != nil {
return err
}
if car.DoorOpen {
return fmt.Errorf("elevator %d opens while already open", car.ID)
}
if car.Floor != ev.Floor {
return fmt.Errorf("OPEN floor mismatch: elevator %d at %s but outputs OPEN-%s",
car.ID, floorName(car.Floor), floorName(ev.Floor))
}
if ev.Time < car.LastPhysicalTime {
return fmt.Errorf("elevator %d opens before previous action finishes", car.ID)
}
if err := j.validateOpenByMode(car, shaft); err != nil {
return err
}
car.DoorOpen = true
car.LastOpenTime = ev.Time
car.LastPhysicalTime = ev.Time
j.openCount++
return nil
}
func (j *judge) handleClose(ev *event) error {
car, shaft, err := j.lookupCar(ev.Elevator)
if err != nil {
return err
}
if err := j.ensureCarActiveAndNotSilent(car, shaft, ev.Kind); err != nil {
return err
}
if err := j.ensureRepAcceptWorkerLock(car, shaft, ev.Kind); err != nil {
return err
}
if !car.DoorOpen {
return fmt.Errorf("elevator %d closes while already closed", car.ID)
}
if car.Floor != ev.Floor {
return fmt.Errorf("CLOSE floor mismatch: elevator %d at %s but outputs CLOSE-%s",
car.ID, floorName(car.Floor), floorName(ev.Floor))
}
if ev.Time-car.LastOpenTime < doorGap {
return fmt.Errorf("elevator %d closes too early: open at %.4f, close at %.4f",
car.ID, seconds(car.LastOpenTime), seconds(ev.Time))
}
if car.OrdinaryLoad > maxLoad {
return fmt.Errorf("elevator %d closes with overload %dkg", car.ID, car.OrdinaryLoad)
}
if err := j.validateCloseByMode(car, shaft); err != nil {
return err
}
car.DoorOpen = false
car.LastPhysicalTime = ev.Time
j.closeCount++
return nil
}
func (j *judge) handleIn(ev *event) error {
car, shaft, err := j.lookupCar(ev.Elevator)
if err != nil {
return err
}
if err := j.ensureCarActiveAndNotSilent(car, shaft, ev.Kind); err != nil {
return err
}
if err := j.ensureRepAcceptWorkerLock(car, shaft, ev.Kind); err != nil {
return err
}
if !car.DoorOpen {
return fmt.Errorf("person %d enters elevator %d while door is closed", ev.PersonID, car.ID)
}
if car.Floor != ev.Floor {
return fmt.Errorf("IN floor mismatch: elevator %d at %s but outputs IN at %s",
car.ID, floorName(car.Floor), floorName(ev.Floor))
}
if req, ok := j.data.Passengers[ev.PersonID]; ok {
if shaft.Mode == modeTest {
return fmt.Errorf("ordinary passenger %d enters during TEST", ev.PersonID)
}
return j.handleOrdinaryIn(req, car, ev.Time)
}
return j.handleWorkerIn(ev.PersonID, car, shaft, ev.Time)
}
func (j *judge) handleOut(ev *event) error {
car, shaft, err := j.lookupCar(ev.Elevator)
if err != nil {
return err
}
if err := j.ensureCarActiveAndNotSilent(car, shaft, ev.Kind); err != nil {
return err
}
if err := j.ensureRepAcceptWorkerLock(car, shaft, ev.Kind); err != nil {
return err
}
if !car.DoorOpen {
return fmt.Errorf("person %d leaves elevator %d while door is closed", ev.PersonID, car.ID)
}
if car.Floor != ev.Floor {
return fmt.Errorf("OUT floor mismatch: elevator %d at %s but outputs OUT at %s",
car.ID, floorName(car.Floor), floorName(ev.Floor))
}
if req, ok := j.data.Passengers[ev.PersonID]; ok {
if shaft.Mode == modeTest {
return fmt.Errorf("ordinary passenger %d leaves during TEST", ev.PersonID)
}
return j.handleOrdinaryOut(req, car, ev.SubKind, ev.Time)
}
return j.handleWorkerOut(ev.PersonID, car, shaft, ev.SubKind, ev.Time)
}
func (j *judge) handleOrdinaryIn(req *passengerReq, car *carState, ts int) error {
ps := j.passengers[req.ID]
if ps.Finished {
return fmt.Errorf("passenger %d already finished", req.ID)
}
if ps.AssignedCar != car.ID {
return fmt.Errorf("passenger %d enters elevator %d without active RECEIVE on it", req.ID, car.ID)
}
if ps.InsideCar != 0 {
return fmt.Errorf("passenger %d already inside elevator %d", req.ID, ps.InsideCar)
}
if ps.OutsideAt != car.Floor {
return fmt.Errorf("passenger %d is at %s but enters elevator %d at %s",
req.ID, floorName(ps.OutsideAt), car.ID, floorName(car.Floor))
}
ps.InsideCar = car.ID
car.OrdinaryIDs[req.ID] = struct{}{}
car.OrdinaryLoad += req.Weight
car.LastPhysicalTime = ts
return nil
}
func (j *judge) handleOrdinaryOut(req *passengerReq, car *carState, sub string, ts int) error {
ps := j.passengers[req.ID]
if ps.InsideCar != car.ID {
return fmt.Errorf("passenger %d leaves elevator %d without being inside it", req.ID, car.ID)
}
if _, ok := car.OrdinaryIDs[req.ID]; !ok {
return fmt.Errorf("passenger %d is missing from elevator %d manifest", req.ID, car.ID)
}
if sub == "S" && car.Floor != req.To {
return fmt.Errorf("passenger %d uses OUT-S at %s but final destination is %s",
req.ID, floorName(car.Floor), floorName(req.To))
}
if sub == "F" && car.Floor == req.To {
return fmt.Errorf("passenger %d uses OUT-F at final floor %s", req.ID, floorName(car.Floor))
}
delete(car.OrdinaryIDs, req.ID)
car.OrdinaryLoad -= req.Weight
ps.InsideCar = 0
ps.AssignedCar = 0
ps.OutsideAt = car.Floor
if sub == "S" {
ps.Finished = true
ps.FinalTime = ts
}
j.dropReceive(car, ts)
car.LastPhysicalTime = ts
return nil
}
func (j *judge) handleWorkerIn(workerID int, car *carState, shaft *shaftState, ts int) error {
if !car.Main {
return fmt.Errorf("worker %d can only enter main elevator", workerID)
}
if shaft.Mode != modeRepAccept || shaft.Maint == nil {
return fmt.Errorf("worker %d enters outside REP_ACCEPT", workerID)
}
if shaft.Maint.WorkerID != workerID {
return fmt.Errorf("worker %d does not match current MAINT worker %d", workerID, shaft.Maint.WorkerID)
}
if car.Floor != f1 {
return fmt.Errorf("worker %d must enter at F1", workerID)
}
if len(car.OrdinaryIDs) != 0 {
return fmt.Errorf("worker %d enters while ordinary passengers remain in elevator %d", workerID, car.ID)
}
ws := j.workers[workerID]
if ws == nil {
ws = &workerState{OutsideAt: f1}
j.workers[workerID] = ws
}
if ws.InCar != 0 {
return fmt.Errorf("worker %d is already inside elevator %d", workerID, ws.InCar)
}
if ws.Finished {
return fmt.Errorf("worker %d already finished", workerID)
}
ws.InCar = car.ID
ws.OutsideAt = f1
car.WorkerIDs[workerID] = struct{}{}
shaft.Maint.Phase = "worker-boarded"
car.LastPhysicalTime = ts
return nil
}
func (j *judge) handleWorkerOut(workerID int, car *carState, shaft *shaftState, sub string, ts int) error {
if sub != "S" {
return fmt.Errorf("worker %d cannot use OUT-F", workerID)
}
if !car.Main || shaft.Mode != modeTest || shaft.Maint == nil {
return fmt.Errorf("worker %d leaves outside TEST", workerID)
}
if shaft.Maint.WorkerID != workerID {
return fmt.Errorf("worker %d does not match active maintenance worker %d", workerID, shaft.Maint.WorkerID)
}
if car.Floor != f1 {
return fmt.Errorf("worker %d must leave at F1", workerID)
}
if _, ok := car.WorkerIDs[workerID]; !ok {
return fmt.Errorf("worker %d is not inside elevator %d", workerID, car.ID)
}
ws := j.workers[workerID]
if ws == nil || ws.InCar != car.ID {
return fmt.Errorf("worker %d runtime state mismatch", workerID)
}
delete(car.WorkerIDs, workerID)
ws.InCar = 0
ws.OutsideAt = f1
ws.Finished = true
shaft.Maint.Phase = "worker-out"
car.LastPhysicalTime = ts
return nil
}
func (j *judge) handleMaintAccept(ev *event) error {
car, shaft, err := j.lookupCar(ev.Elevator)
if err != nil {
return err
}
if !car.Main {
return fmt.Errorf("MAINT-ACCEPT must target main elevator, got %d", car.ID)
}
if shaft.Mode != modeNormal {
return fmt.Errorf("MAINT-ACCEPT on shaft %d while mode is %s", shaft.ID, modeName(shaft.Mode))
}
req, err := j.nextMaint(ev.Elevator, ev.WorkerID, ev.Target, ev.Time)
if err != nil {
return err
}
shaft.Maint = &maintRun{
AcceptAt: ev.Time,
WorkerID: req.WorkerID,
Target: req.Target,
Phase: "accept",
}
j.workers[req.WorkerID] = &workerState{OutsideAt: f1}
j.setMode(shaft, modeRepAccept, ev.Time)
return nil
}
func (j *judge) handleMaint1Begin(ev *event) error {
car, shaft, err := j.lookupCar(ev.Elevator)
if err != nil {
return err
}
if !car.Main || shaft.Mode != modeRepAccept || shaft.Maint == nil {
return fmt.Errorf("MAINT1-BEGIN on elevator %d outside REP_ACCEPT", ev.Elevator)
}
if car.Floor != f1 || car.DoorOpen {
return fmt.Errorf("MAINT1-BEGIN requires closed F1 state on elevator %d", ev.Elevator)
}
if len(car.OrdinaryIDs) != 0 {
return fmt.Errorf("MAINT1-BEGIN requires no ordinary passengers in elevator %d", ev.Elevator)
}
if len(car.WorkerIDs) != 1 {
return fmt.Errorf("MAINT1-BEGIN requires exactly one worker in elevator %d", ev.Elevator)
}
if shaft.Maint.Phase != "closed-after-worker" {
return fmt.Errorf("MAINT1-BEGIN must happen after worker boards and elevator closes")
}
j.endAllReceivesOnCar(car, ev.Time)
shaft.Maint.BeginAt = ev.Time
shaft.Maint.Phase = "repair"
j.setMode(shaft, modeRepair, ev.Time)
car.LastPhysicalTime = ev.Time
return nil
}
func (j *judge) handleMaint2Begin(ev *event) error {
car, shaft, err := j.lookupCar(ev.Elevator)
if err != nil {
return err
}
if !car.Main || shaft.Mode != modeRepair || shaft.Maint == nil {
return fmt.Errorf("MAINT2-BEGIN on elevator %d outside REPAIR", ev.Elevator)
}
if ev.Time-shaft.Maint.BeginAt < stopGap {
return fmt.Errorf("MAINT2-BEGIN on elevator %d happens before 1s repair delay", ev.Elevator)
}
if car.Floor != f1 || car.DoorOpen {
return fmt.Errorf("MAINT2-BEGIN requires elevator %d to stay closed at F1", ev.Elevator)
}
if len(car.WorkerIDs) != 1 {
return fmt.Errorf("MAINT2-BEGIN requires worker to stay inside elevator %d", ev.Elevator)
}
shaft.Maint.Phase = "to-target"
j.setMode(shaft, modeTest, ev.Time)
car.LastPhysicalTime = ev.Time
return nil
}
func (j *judge) handleMaintEnd(ev *event) error {
car, shaft, err := j.lookupCar(ev.Elevator)
if err != nil {
return err
}
if !car.Main || shaft.Mode != modeTest || shaft.Maint == nil {
return fmt.Errorf("MAINT-END on elevator %d outside TEST", ev.Elevator)
}
if ev.Time-shaft.Maint.AcceptAt > maintLimit {
return fmt.Errorf("MAINT response on elevator %d exceeds 7s", ev.Elevator)
}
if car.Floor != f1 || car.DoorOpen {
return fmt.Errorf("MAINT-END requires elevator %d to be closed at F1", ev.Elevator)
}
if shaft.Maint.Phase != "closed-after-worker-out" {
return fmt.Errorf("MAINT-END requires worker to exit and door to close first")
}
shaft.Maint = nil
j.setMode(shaft, modeNormal, ev.Time)
car.LastPhysicalTime = ev.Time
return nil
}
func (j *judge) handleUpdateAccept(ev *event) error {
car, shaft, err := j.lookupCar(ev.Elevator)
if err != nil {
return err
}
if !car.Main {
return fmt.Errorf("UPDATE-ACCEPT must target a main elevator")
}
if shaft.Mode != modeNormal {
return fmt.Errorf("UPDATE-ACCEPT on shaft %d while mode is %s", shaft.ID, modeName(shaft.Mode))
}
if _, err := j.nextUpdate(ev.Elevator, ev.Time); err != nil {
return err
}
shaft.Update = &updateRun{AcceptAt: ev.Time}
j.setMode(shaft, modeUpAccept, ev.Time)
return nil
}
func (j *judge) handleUpdateBegin(ev *event) error {
car, shaft, err := j.lookupCar(ev.Elevator)
if err != nil {
return err
}
if !car.Main || shaft.Mode != modeUpAccept || shaft.Update == nil {
return fmt.Errorf("UPDATE-BEGIN on elevator %d outside UP_ACCEPT", ev.Elevator)
}
if car.Floor != f3 || car.DoorOpen {
return fmt.Errorf("UPDATE-BEGIN requires main elevator %d to be closed at F3", ev.Elevator)
}
if len(car.OrdinaryIDs) != 0 || len(car.WorkerIDs) != 0 {
return fmt.Errorf("UPDATE-BEGIN requires main elevator %d to be empty", ev.Elevator)
}
j.endAllReceivesOnCar(car, ev.Time)
shaft.Update.BeginAt = ev.Time
j.setMode(shaft, modeUpdate, ev.Time)
car.LastPhysicalTime = ev.Time
return nil
}
func (j *judge) handleUpdateEnd(ev *event) error {
car, shaft, err := j.lookupCar(ev.Elevator)
if err != nil {
return err
}
if !car.Main || shaft.Mode != modeUpdate || shaft.Update == nil {
return fmt.Errorf("UPDATE-END on elevator %d outside UPDATE", ev.Elevator)
}
if ev.Time-shaft.Update.BeginAt < stopGap {
return fmt.Errorf("UPDATE-END on elevator %d happens before 1s update delay", ev.Elevator)
}
if ev.Time-shaft.Update.AcceptAt > updateLimit {
return fmt.Errorf("UPDATE response on elevator %d exceeds 6s", ev.Elevator)
}
if car.Floor != f3 || car.DoorOpen {
return fmt.Errorf("UPDATE-END requires main elevator %d to stay closed at F3", ev.Elevator)
}
backup := j.cars[shaft.ID+6]
backup.Active = true
backup.Floor = f1
backup.DoorOpen = false
backup.LastPhysicalTime = ev.Time
j.setMode(shaft, modeDouble, ev.Time)
shaft.Update = nil
car.LastPhysicalTime = ev.Time
return j.checkShaftOrdering(shaft.ID)
}
func (j *judge) handleRecycleAccept(ev *event) error {
car, shaft, err := j.lookupCar(ev.Elevator)
if err != nil {
return err
}
if car.Main {
return fmt.Errorf("RECYCLE-ACCEPT must target backup elevator")
}
if shaft.Mode != modeDouble {
return fmt.Errorf("RECYCLE-ACCEPT on shaft %d while mode is %s", shaft.ID, modeName(shaft.Mode))
}
if _, err := j.nextRecycle(ev.Elevator, ev.Time); err != nil {
return err
}
shaft.Recycle = &recycleRun{AcceptAt: ev.Time}
j.setMode(shaft, modeRecAccept, ev.Time)
return nil
}
func (j *judge) handleRecycleBegin(ev *event) error {
car, shaft, err := j.lookupCar(ev.Elevator)
if err != nil {
return err
}
if car.Main || shaft.Mode != modeRecAccept || shaft.Recycle == nil {
return fmt.Errorf("RECYCLE-BEGIN on elevator %d outside REC_ACCEPT", ev.Elevator)
}
if car.Floor != f1 || car.DoorOpen {
return fmt.Errorf("RECYCLE-BEGIN requires backup elevator %d to be closed at F1", ev.Elevator)
}
if len(car.OrdinaryIDs) != 0 || len(car.WorkerIDs) != 0 {
return fmt.Errorf("RECYCLE-BEGIN requires backup elevator %d to be empty", ev.Elevator)
}
j.endAllReceivesOnCar(car, ev.Time)
shaft.Recycle.BeginAt = ev.Time
j.setMode(shaft, modeRecycle, ev.Time)
car.LastPhysicalTime = ev.Time
return nil
}
func (j *judge) handleRecycleEnd(ev *event) error {
car, shaft, err := j.lookupCar(ev.Elevator)
if err != nil {
return err
}
if car.Main || shaft.Mode != modeRecycle || shaft.Recycle == nil {
return fmt.Errorf("RECYCLE-END on elevator %d outside RECYCLE", ev.Elevator)
}
if ev.Time-shaft.Recycle.BeginAt < stopGap {
return fmt.Errorf("RECYCLE-END on elevator %d happens before 1s recycle delay", ev.Elevator)
}
if ev.Time-shaft.Recycle.AcceptAt > recycleLimit {
return fmt.Errorf("RECYCLE response on elevator %d exceeds 6s", ev.Elevator)
}
if car.Floor != f1 || car.DoorOpen {
return fmt.Errorf("RECYCLE-END requires backup elevator %d to be closed at F1", ev.Elevator)
}
car.Active = false
car.Floor = f1
car.DoorOpen = false
car.LastPhysicalTime = ev.Time
shaft.Recycle = nil
j.setMode(shaft, modeNormal, ev.Time)
return nil
}
func (j *judge) ensureCarActiveAndNotSilent(car *carState, shaft *shaftState, action string) error {
if !car.Active {
return fmt.Errorf("elevator %d is inactive but outputs %s", car.ID, action)
}
switch shaft.Mode {
case modeRepair:
return fmt.Errorf("elevator %d is silent during REPAIR but outputs %s", car.ID, action)
case modeUpdate:
return fmt.Errorf("shaft %d is silent during UPDATE but elevator %d outputs %s", shaft.ID, car.ID, action)
case modeRecycle:
if !car.Main {
return fmt.Errorf("backup elevator %d is silent during RECYCLE but outputs %s", car.ID, action)
}
}
return nil
}
func (j *judge) ensureRepAcceptWorkerLock(car *carState, shaft *shaftState, action string) error {
if !car.Main || shaft.Mode != modeRepAccept || shaft.Maint == nil {
return nil
}
switch shaft.Maint.Phase {
case "worker-boarded":
if action != "CLOSE" {
return fmt.Errorf("after worker boards, only CLOSE is allowed before MAINT1-BEGIN")
}
case "closed-after-worker":
if action != "MAINT1-BEGIN" {
return fmt.Errorf("after worker boards and door closes, only MAINT1-BEGIN is allowed")
}
}
return nil
}
func (j *judge) validateArriveByMode(car *carState, shaft *shaftState, prev, next floor) error {
if shaft.Mode == modeTest {
return j.validateTestArrive(shaft, prev, next)
}
return j.ensureRangeByMode(car, shaft.Mode)
}
func (j *judge) validateTestArrive(shaft *shaftState, prev, next floor) error {
if shaft.Maint == nil {
return errors.New("missing maintenance context in TEST")
}
switch shaft.Maint.Phase {
case "to-target":
want := stepToward(prev, shaft.Maint.Target)
if next != want {
return fmt.Errorf("TEST route should move from %s to %s, but arrives at %s",
floorName(prev), floorName(want), floorName(next))
}
if next == shaft.Maint.Target {
shaft.Maint.Phase = "to-f1"
}
case "to-f1":
want := stepToward(prev, f1)
if next != want {
return fmt.Errorf("TEST return route should move from %s to %s, but arrives at %s",
floorName(prev), floorName(want), floorName(next))
}
default:
return fmt.Errorf("TEST forbids ARRIVE while maintenance phase is %s", shaft.Maint.Phase)
}
return nil
}
func (j *judge) validateOpenByMode(car *carState, shaft *shaftState) error {
if shaft.Mode == modeTest {
if !car.Main || car.Floor != f1 || shaft.Maint == nil || shaft.Maint.Phase != "to-f1" {
return fmt.Errorf("during TEST only one OPEN-F1 is allowed after returning from target floor")
}
shaft.Maint.Phase = "worker-exit-open"
return nil
}
return j.ensureRangeByMode(car, shaft.Mode)
}
func (j *judge) validateCloseByMode(car *carState, shaft *shaftState) error {
if shaft.Mode == modeRepAccept && car.Main && shaft.Maint != nil && shaft.Maint.Phase == "worker-boarded" {
if car.Floor != f1 {
return fmt.Errorf("after worker boards, elevator %d must close at F1", car.ID)
}
shaft.Maint.Phase = "closed-after-worker"
}
if shaft.Mode == modeTest {
if !car.Main || car.Floor != f1 || shaft.Maint == nil || shaft.Maint.Phase != "worker-out" {
return fmt.Errorf("during TEST CLOSE is only allowed after worker exits at F1")
}
shaft.Maint.Phase = "closed-after-worker-out"
}
return nil
}
func (j *judge) ensureRangeByMode(car *carState, m mode) error {
switch m {
case modeDouble, modeRecAccept, modeRecycle:
return j.ensureDoubleRange(car)
default:
return j.ensureNormalRange(car)
}
}
func (j *judge) ensureNormalRange(car *carState) error {
if car.Floor < b4 || car.Floor > f7 {
return fmt.Errorf("elevator %d leaves building range at %s", car.ID, floorName(car.Floor))
}
return nil
}
func (j *judge) ensureDoubleRange(car *carState) error {
if car.Main {
if car.Floor < f2 || car.Floor > f7 {
return fmt.Errorf("main elevator %d leaves F2-F7 range at %s", car.ID, floorName(car.Floor))
}
return nil
}
if car.Floor < b4 || car.Floor > f2 {
return fmt.Errorf("backup elevator %d leaves B4-F2 range at %s", car.ID, floorName(car.Floor))
}
return nil
}
func (j *judge) moveDuration(car *carState, shaft *shaftState) int {
if car.Main && shaft.Mode == modeTest {
return moveTest
}
return moveNormal
}
func (j *judge) moveAllowedAtStart(car *carState, from, to floor, start int) bool {
shaft := j.shafts[car.Shaft]
m := modeAt(shaft.ModeHistory, start)
if valueAt(car.ReceiveHistory, start) > 0 {
return true
}
if hasCommandMovePrivilege(m, car.Main) {
return true
}
if (m == modeDouble || m == modeRecAccept || m == modeRecycle) && from == f2 && absInt(int(to-from)) == 1 {
return true
}
return false
}
func (j *judge) checkShaftOrdering(shaftID int) error {
shaft := j.shafts[shaftID]
if shaft.Mode != modeDouble && shaft.Mode != modeRecAccept && shaft.Mode != modeRecycle {
return nil
}
mainCar := j.cars[shaftID]
backup := j.cars[shaftID+6]
if !mainCar.Active || !backup.Active {
return nil
}
if !(mainCar.Floor > backup.Floor) {
return fmt.Errorf("shaft %d has invalid double-car positions: main at %s, backup at %s",
shaftID, floorName(mainCar.Floor), floorName(backup.Floor))
}
return nil
}
func (j *judge) lookupCar(id int) (*carState, *shaftState, error) {
car := j.cars[id]
if car == nil {
return nil, nil, fmt.Errorf("unknown elevator %d", id)
}
return car, j.shafts[car.Shaft], nil
}
func (j *judge) nextMaint(eid, wid int, target floor, ts int) (*maintReq, error) {
list := j.data.MaintsByElevator[eid]
idx := j.maintIndex[eid]
if idx >= len(list) {
return nil, fmt.Errorf("unexpected MAINT-ACCEPT for elevator %d", eid)
}
req := list[idx]
if req.WorkerID != wid || req.Target != target {
return nil, fmt.Errorf("MAINT-ACCEPT does not match input for elevator %d", eid)
}
j.maintIndex[eid] = idx + 1
return &req, nil
}
func (j *judge) nextUpdate(eid int, ts int) (*updateReq, error) {
list := j.data.UpdatesByElevator[eid]
idx := j.updateIndex[eid]
if idx >= len(list) {
return nil, fmt.Errorf("unexpected UPDATE-ACCEPT for elevator %d", eid)
}
req := list[idx]
j.updateIndex[eid] = idx + 1
return &req, nil
}
func (j *judge) nextRecycle(eid int, ts int) (*recycleReq, error) {
list := j.data.RecyclesByBackup[eid]
idx := j.recycleIndex[eid]
if idx >= len(list) {
return nil, fmt.Errorf("unexpected RECYCLE-ACCEPT for elevator %d", eid)
}
req := list[idx]
j.recycleIndex[eid] = idx + 1
return &req, nil
}
func (j *judge) endAllReceivesOnCar(car *carState, ts int) {
if car.ReceiveCount == 0 {
return
}
for _, ps := range j.passengers {
if ps.AssignedCar == car.ID {
ps.AssignedCar = 0
}
}
car.ReceiveCount = 0
pushTimeValue(&car.ReceiveHistory, ts, 0)
}
func (j *judge) dropReceive(car *carState, ts int) {
if car.ReceiveCount > 0 {
car.ReceiveCount--
}
pushTimeValue(&car.ReceiveHistory, ts, car.ReceiveCount)
}
func (j *judge) setMode(shaft *shaftState, m mode, ts int) {
shaft.Mode = m
pushModeValue(&shaft.ModeHistory, ts, m)
}
func (j *judge) finish(realTime, cpuTime, tmax float64) (*metrics, error) {
for _, id := range j.data.PassengerIDs {
ps := j.passengers[id]
if !ps.Finished {
return nil, fmt.Errorf("passenger %d does not reach final destination", id)
}
if ps.AssignedCar != 0 || ps.InsideCar != 0 {
return nil, fmt.Errorf("passenger %d still has unfinished runtime state", id)
}
}
for id, ws := range j.workers {
if ws.InCar != 0 {
return nil, fmt.Errorf("worker %d is still inside elevator %d", id, ws.InCar)
}
}
for id, car := range j.cars {
if car.DoorOpen {
return nil, fmt.Errorf("elevator %d ends with door open", id)
}
if len(car.OrdinaryIDs) != 0 || len(car.WorkerIDs) != 0 {
return nil, fmt.Errorf("elevator %d still contains passengers", id)
}
if car.ReceiveCount != 0 {
return nil, fmt.Errorf("elevator %d still has unfinished RECEIVE count %d", id, car.ReceiveCount)
}
}
for shaftID, shaft := range j.shafts {
if shaft.Mode != modeNormal {
return nil, fmt.Errorf("shaft %d does not return to NORMAL, current mode %s", shaftID, modeName(shaft.Mode))
}
}
if err := j.checkAcceptConsumption(); err != nil {
return nil, err
}
tavg, err := j.computeTavg()
if err != nil {
return nil, err
}
w := float64(j.arriveCount)*0.4 + float64(j.openCount+j.closeCount)*0.1
trun := seconds(j.lastOutputTime)
if realTime >= 0 && realTime > trun {
trun = realTime
}
return &metrics{Trun: trun, Tavg: tavg, W: w}, nil
}
func (j *judge) checkAcceptConsumption() error {
for eid, list := range j.data.MaintsByElevator {
if j.maintIndex[eid] != len(list) {
return fmt.Errorf("missing MAINT-ACCEPT for elevator %d", eid)
}
}
for eid, list := range j.data.UpdatesByElevator {
if j.updateIndex[eid] != len(list) {
return fmt.Errorf("missing UPDATE-ACCEPT for elevator %d", eid)
}
}
for eid, list := range j.data.RecyclesByBackup {
if j.recycleIndex[eid] != len(list) {
return fmt.Errorf("missing RECYCLE-ACCEPT for elevator %d", eid)
}
}
return nil
}
func (j *judge) computeTavg() (float64, error) {
if len(j.data.PassengerIDs) == 0 {
return 0, nil
}
total := 0
for _, id := range j.data.PassengerIDs {
req := j.data.Passengers[id]
ps := j.passengers[id]
if !ps.Finished {
return 0, fmt.Errorf("passenger %d does not finish", id)
}
total += ps.FinalTime - req.Time
}
return float64(total) / float64(len(j.data.PassengerIDs)) / tick, nil
}
func splitTimestamp(line string, maxDigits int) (string, int, error) {
left := strings.IndexByte(line, '[')
right := strings.IndexByte(line, ']')
if left != 0 || right < 0 || right+1 >= len(line) {
return "", 0, fmt.Errorf("bad line format %q", line)
}
ts, err := parseTime(line[1:right], maxDigits)
if err != nil {
return "", 0, err
}
return strings.TrimSpace(line[right+1:]), ts, nil
}
func parseTime(raw string, maxDigits int) (int, error) {
s := strings.TrimSpace(raw)
if s == "" {
return 0, errors.New("empty timestamp")
}
parts := strings.Split(s, ".")
if len(parts) == 1 {
parts = append(parts, "")
}
if len(parts) != 2 {
return 0, fmt.Errorf("bad timestamp %q", raw)
}
intPart, err := strconv.Atoi(parts[0])
if err != nil {
return 0, fmt.Errorf("bad timestamp %q", raw)
}
frac := parts[1]
if len(frac) > maxDigits {
return 0, fmt.Errorf("timestamp %q has too many decimal digits", raw)
}
if len(frac) > 4 {
return 0, fmt.Errorf("timestamp %q has too many decimal digits", raw)
}
for len(frac) < 4 {
frac += "0"
}
fracPart, err := strconv.Atoi(frac)
if err != nil {
return 0, fmt.Errorf("bad timestamp %q", raw)
}
return intPart*tick + fracPart, nil
}
func parseFloor(name string) (floor, error) {
f, ok := floorIndex[name]
if !ok {
return 0, fmt.Errorf("bad floor %q", name)
}
return f, nil
}
func carCanReceive(m mode, main bool) bool {
switch m {
case modeNormal, modeRepAccept, modeUpAccept:
return main
case modeDouble, modeRecAccept:
return true
case modeRecycle:
return main
default:
return false
}
}
func hasCommandMovePrivilege(m mode, main bool) bool {
switch m {
case modeRepAccept, modeRepair, modeTest, modeUpAccept:
return main
case modeRecAccept:
return !main
default:
return false
}
}
func modeAt(history []modeValue, ts int) mode {
cur := history[0].Value
for _, item := range history {
if item.Time > ts {
break
}
cur = item.Value
}
return cur
}
func valueAt(history []timeValue, ts int) int {
cur := history[0].Value
for _, item := range history {
if item.Time > ts {
break
}
cur = item.Value
}
return cur
}
func pushTimeValue(history *[]timeValue, ts, value int) {
h := *history
if len(h) > 0 && h[len(h)-1].Time == ts {
h[len(h)-1].Value = value
*history = h
return
}
*history = append(h, timeValue{Time: ts, Value: value})
}
func pushModeValue(history *[]modeValue, ts int, value mode) {
h := *history
if len(h) > 0 && h[len(h)-1].Time == ts {
h[len(h)-1].Value = value
*history = h
return
}
*history = append(h, modeValue{Time: ts, Value: value})
}
func stepToward(cur, target floor) floor {
switch {
case cur < target:
return cur + 1
case cur > target:
return cur - 1
default:
return cur
}
}
func seconds(v int) float64 {
return float64(v) / tick
}
func floorName(f floor) string {
return floorNames[int(f)]
}
func modeName(m mode) string {
switch m {
case modeNormal:
return "NORMAL"
case modeRepAccept:
return "REP_ACCEPT"
case modeRepair:
return "REPAIR"
case modeTest:
return "TEST"
case modeUpAccept:
return "UP_ACCEPT"
case modeUpdate:
return "UPDATE"
case modeDouble:
return "DOUBLE"
case modeRecAccept:
return "REC_ACCEPT"
case modeRecycle:
return "RECYCLE"
default:
return "UNKNOWN"
}
}
func atoi(raw, name string) (int, error) {
v, err := strconv.Atoi(raw)
if err != nil {
return 0, fmt.Errorf("bad %s %q", name, raw)
}
return v, nil
}
func absInt(v int) int {
if v < 0 {
return -v
}
return v
}
func failf(format string, args ...any) {
fmt.Fprintf(os.Stderr, format+"\n", args...)
os.Exit(1)
}