You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Simpy医院系统模拟资源请求异常:患者数量统计不符问题修复

医院模拟系统Simpy资源请求问题修复

我是Simpy新手,开发简易医院模拟系统时遇到资源请求逻辑错误:模拟结束后总就诊患者数大于已诊断患者数+候诊患者数之和。系统逻辑为患者持续进入候诊室,多名医生处理候诊队列,接诊后花费时间诊断。怀疑HospitalManager中候诊室与医生的资源请求逻辑有误,求修复代码。

原代码如下:

import random
from dataclasses import dataclass
from datetime import datetime
from uuid import UUID, uuid4

from faker import Faker
from simpy import Environment, Resource, Store


@dataclass
class Patient:
    id: UUID
    created_at: int | float
    name: str
    address: str
    sex: str
    birthdate: datetime


class WaitingRoom:
    def __init__(self, env: Environment):
        self._patients = Store(env)

    def put(self, patient: Patient):
        self._patients.put(patient)

    def get(self):
        return self._patients.get()


class DoctorManager:
    def __init__(self, env: Environment, num_doctors: int, diagnosis_time: float):
        self._doctors = Resource(env, capacity=num_doctors)
        self._diagnosis_time = diagnosis_time

    def diagnose(self, env: Environment, patient: Patient):
        yield env.timeout(random.expovariate(1.0 / self._diagnosis_time))
        print(env.now, patient, "is diagnosed.")


class HospitalManager:
    def __init__(self, waiting_room: WaitingRoom, doctor_manager: DoctorManager):
        self._waiting_room = waiting_room
        self._doctor_manager = doctor_manager
        self._diagnosed_patients_number = 0

    def add_patient_to_waiting_room(self, patient: Patient):
        self._waiting_room.put(patient)

    def invite_patient_to_doctor(self, env: Environment):
        with self._waiting_room.get() as patient_req:
            with self._doctor_manager._doctors.request() as doctor_req:
                yield doctor_req
                yield patient_req
                yield env.process(self._doctor_manager.diagnose(env, patient_req.value))
                self._diagnosed_patients_number += 1

    def keep_inviting_patient_to_doctor(self, env: Environment):
        while True:
            yield env.timeout(1)
            env.process(self.invite_patient_to_doctor(env))

    def monitor_waiting_room(self, env: Environment):
        while True:
            print(
                env.now,
                len(self._waiting_room._patients.items),
                "patients are waiting",
            )
            print(f"Diagnosed patients: {self._diagnosed_patients_number}")
            yield env.timeout(5)


class Ecosystem:
    def __init__(self, patient_visit_time: float, hospital_manager: HospitalManager):
        self._patient_visit_time = patient_visit_time
        self._hospital_manager = hospital_manager
        self._patient_provider = Faker()

    def run(self, env: Environment, until):
        env.process(self._continue_to_generate_patients(env))
        env.process(self._hospital_manager.keep_inviting_patient_to_doctor(env))
        env.process(self._hospital_manager.monitor_waiting_room(env))
        env.run(until=until)

    def _generate_patient(self, env: Environment):
        profile = self._patient_provider.simple_profile()
        return Patient(
            id=uuid4(),
            created_at=env.now,
            name=profile["name"],
            address=profile["address"],
            sex=profile["sex"],
            birthdate=profile["birthdate"],
        )

    def _continue_to_generate_patients(self, env: Environment):
        patient_number = 0
        while True:
            yield env.timeout(random.expovariate(1.0 / self._patient_visit_time))
            patient_number += 1
            print(f"Total Visiting Patients: {patient_number}")
            patient = self._generate_patient(env)
            self._hospital_manager.add_patient_to_waiting_room(patient)


if __name__ == "__main__":
    env = Environment()
    waiting_room = WaitingRoom(env)
    doctor_manager = DoctorManager(env, 3, 10)
    hospital_manager = HospitalManager(waiting_room, doctor_manager)
    ecosystem = Ecosystem(1, hospital_manager)
    ecosystem.run(env, 1000)

问题分析

  1. 资源请求顺序错误:invite_patient_to_doctor中先请求医生资源,再获取候诊患者。这会导致医生资源被占用后,若候诊室无患者,医生会被空耗;同时,当模拟结束时,部分已被取走候诊室但未完成诊断的患者,既不在候诊室统计中,也未计入已诊断数,导致总数不符。
  2. 冗余进程堆积:keep_inviting_patient_to_doctor每隔1秒启动新的接诊进程,会导致大量进程等待资源,加剧患者“丢失”的情况,同时浪费系统资源。

修复后代码

import random
from dataclasses import dataclass
from datetime import datetime
from uuid import UUID, uuid4

from faker import Faker
from simpy import Environment, Resource, Store


@dataclass
class Patient:
    id: UUID
    created_at: int | float
    name: str
    address: str
    sex: str
    birthdate: datetime


class WaitingRoom:
    def __init__(self, env: Environment):
        self._patients = Store(env)

    def put(self, patient: Patient):
        self._patients.put(patient)

    def get(self):
        return self._patients.get()

    @property
    def waiting_count(self):
        # 提供对外的候诊人数统计接口,避免直接访问私有属性
        return len(self._patients.items)


class DoctorManager:
    def __init__(self, env: Environment, num_doctors: int, diagnosis_time: float):
        self._doctors = Resource(env, capacity=num_doctors)
        self._diagnosis_time = diagnosis_time

    def diagnose(self, env: Environment, patient: Patient):
        yield env.timeout(random.expovariate(1.0 / self._diagnosis_time))
        print(f"{env.now} - {patient.name} is diagnosed.")


class HospitalManager:
    def __init__(self, waiting_room: WaitingRoom, doctor_manager: DoctorManager):
        self._waiting_room = waiting_room
        self._doctor_manager = doctor_manager
        self._diagnosed_patients_number = 0
        self._diagnosing_patients_number = 0  # 新增:统计正在诊断的患者数

    def add_patient_to_waiting_room(self, patient: Patient):
        self._waiting_room.put(patient)

    def process_patient(self, env: Environment):
        # 重构接诊逻辑:先取患者,再请求医生,避免资源空占
        while True:
            # 先获取候诊患者,无患者则等待
            patient = yield self._waiting_room.get()
            
            # 请求医生资源,有空闲医生则开始诊断
            with self._doctor_manager._doctors.request() as doctor_req:
                yield doctor_req
                self._diagnosing_patients_number += 1
                yield env.process(self._doctor_manager.diagnose(env, patient))
                self._diagnosing_patients_number -= 1
                self._diagnosed_patients_number += 1

    def start_doctor_processes(self, env: Environment):
        # 根据医生数量启动对应数量的接诊进程,避免冗余进程
        for _ in range(self._doctor_manager._doctors.capacity):
            env.process(self.process_patient(env))

    def monitor_waiting_room(self, env: Environment):
        while True:
            waiting = self._waiting_room.waiting_count
            diagnosing = self._diagnosing_patients_number
            diagnosed = self._diagnosed_patients_number
            print(f"{env.now} - Waiting: {waiting}, Diagnosing: {diagnosing}, Diagnosed: {diagnosed}")
            print(f"Total tracked patients: {waiting + diagnosing + diagnosed}")
            yield env.timeout(5)


class Ecosystem:
    def __init__(self, patient_visit_time: float, hospital_manager: HospitalManager):
        self._patient_visit_time = patient_visit_time
        self._hospital_manager = hospital_manager
        self._patient_provider = Faker()
        self._total_patients = 0  # 统一统计总就诊数,避免之前的局部变量问题

    def run(self, env: Environment, until):
        env.process(self._continue_to_generate_patients(env))
        self._hospital_manager.start_doctor_processes(env)
        env.process(self._hospital_manager.monitor_waiting_room(env))
        env.run(until=until)
        # 模拟结束后打印最终统计
        waiting = self._hospital_manager._waiting_room.waiting_count
        diagnosing = self._hospital_manager._diagnosing_patients_number
        diagnosed = self._hospital_manager._diagnosed_patients_number
        print(f"\n=== Simulation End ===")
        print(f"Total Visiting Patients: {self._total_patients}")
        print(f"Waiting: {waiting}, Diagnosing: {diagnosing}, Diagnosed: {diagnosed}")
        print(f"Total Tracked: {waiting + diagnosing + diagnosed}")

    def _generate_patient(self, env: Environment):
        profile = self._patient_provider.simple_profile()
        return Patient(
            id=uuid4(),
            created_at=env.now,
            name=profile["name"],
            address=profile["address"],
            sex=profile["sex"],
            birthdate=profile["birthdate"],
        )

    def _continue_to_generate_patients(self, env: Environment):
        while True:
            yield env.timeout(random.expovariate(1.0 / self._patient_visit_time))
            self._total_patients += 1
            print(f"{env.now} - New patient arrived! Total: {self._total_patients}")
            patient = self._generate_patient(env)
            self._hospital_manager.add_patient_to_waiting_room(patient)


if __name__ == "__main__":
    env = Environment()
    waiting_room = WaitingRoom(env)
    doctor_manager = DoctorManager(env, 3, 10)
    hospital_manager = HospitalManager(waiting_room, doctor_manager)
    ecosystem = Ecosystem(1, hospital_manager)
    ecosystem.run(env, 1000)

关键修改说明

  1. 调整资源获取顺序:将接诊逻辑改为先获取候诊患者,再请求医生资源,确保只有当有患者时才占用医生,避免资源空耗,同时保证患者不会被提前取走而未被统计。
  2. 优化进程启动逻辑:根据医生数量启动对应数量的接诊进程,每个进程循环处理患者,避免原代码中频繁启动新进程导致的冗余堆积。
  3. 完善统计维度:新增正在诊断的患者统计,模拟结束时统一打印总就诊数、候诊数、诊断中数、已诊断数,确保总数完全匹配。
  4. 封装统计接口:给WaitingRoom添加waiting_count属性,避免直接访问私有属性,提高代码封装性。
  5. 统一总就诊数统计:将总就诊数移到Ecosystem的成员变量中,避免原代码局部变量可能导致的统计误差。

内容的提问来源于stack exchange,提问作者Shuhei Kishi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 00:08:09