2019年深圳杯数学建模A题深圳居民健康水平评估与测控模型研究解题全过程文档及程序

2019年深圳杯数学建模

A题 深圳居民健康水平评估与测控模型研究

原题再现:

  一、研究背景
  根据国家《"健康中国2030"规划纲要》,中国卫生健康事业发展方式正从以"治病为中心"向以"健康为中心"转变。因为当下的我们面临了这样的问题:以旧石器时代的代谢系统、农业社会的饮食习惯和意识观念、迎接着工业化时代技术与物质浪潮的冲击。人们不仅身体层面被慢性非传染性疾病困扰(哈佛大学人类进化生物学教授Daniel E. Lieberman称之为"进化失配"导致的"失配性疾病":癌症、糖尿病、脑卒中、高血压、高血脂、以及精神性疾病等),同时也遭遇了严重的水污染、空气污染、垃圾围城等现代环境问题。依靠"治病为中心"的末端应对方式已无法解决这些问题,因而需要采取"以健康为中心"的全过程测控策略、以"社会共治"模式谋求解脱之道:政府努力提升公共卫生健康服务水准,同时为居民参与社会和环境健康共治创造条件,引导居民不断提升自我健康管理意识与能力,实现更高水平的全民健康,让居民能够在健康环境里获得适宜、连续的综合型健康服务。
  世界卫生组织(World Health Organization,WHO)对健康的定义是:"健康不仅为疾病或羸弱之消除,而系体格,精神与社会之完全健康状态"。自1948年以来,该定义从未被修订;1989年WHO进一步提出四维健康新概念,包括躯体健康、心理健康、社会健康和道德健康。在以健康为中心的社会卫生健康共治系统里,政府应建立公共卫生与环境健康水平的动态评估机制,能够对全体居民全生命周期健康水平进行持续、动态的量化评估,分析影响居民健康态势的各种因素,结合大健康学科前沿成果(例如肠道菌群与慢性病关系等研究领域),构建现代医学与中国传统医学的融合架构,努力探索、创新人体健康靶向干预措施,对影响公共卫生健康的社会习俗、饮食习惯、卫生环境和医疗服务等因素进行综合管控。与此同时,通过推进社会卫生健康服务领域信息的全面互联互通,逐步形成个体全生命周期健康信息管理机制,实现居民在各类医疗机构就诊信息、健康管理与服务机构服务信息、学生健康与体质监测信息以及个体自我健康监测信息的全面融合,形成居民卫生健康状况大数据系统;建立与整体公共健康水平评估机制紧密关联的个性化居民健康水平动态评估模型,对居民个体提供健康指导和个性化的靶向干预服务,促进居民基于个性化健康评估模型形成积极的自我健康管理习惯,进而提高整体公共卫生健康水平。
  二、问题的描述
  深圳市政府围绕普及健康生活、优化健康服务、完善健康保障、建设健康环境、发展健康产业,积极实施重点领域健康行动计划,即《"健康深圳"行动计划(2017-2020)》,努力打造健康中国的"深圳样板"。城市的健康主要是城市居民的健康,居民的健康主要涉及到生活、环境、卫生、医疗、文体等诸多方面。现在深圳市聘请你们团队结合深圳的实际情况,收集相关数据,分析研究下列问题:
  1. 利用深圳市的相关数据,从影响"健康深圳"的生活、环境(诸如空气污染、垃圾围城等问题)、卫生、医疗、文体等方面因素入手,定量分析深圳居民健康水平的现状发展趋势、评估"健康深圳行动计划"内容对深圳居民健康水平发展趋势的影响作用。
  2. 随着经济的发展,具有所谓"失配性"现象的慢性非传染疾病不仅越来越多,而且有年青化的趋势。请尝试结合大健康学科前沿成果分析这些现象的形成机理,综合考虑饮食习惯、生活方式、工作压力和医疗保障等方面的影响,建立能够支持健康水平动态测控需求的社会公共卫生与环境评估模型和居民个体评估模型,并从政策环境和居民自身角度,提出有效解决类似"失配性"慢性非传染疾病、以及环境污染问题的可行方案。
  3. 据相关调查显示,目前深圳居民的脂肪供能比高出全国平均水平,而高胆固醇血症患病率是全国水平的数倍。如果能够采取有效的干预措施使高血脂人群的血脂降低,则不仅能提升深圳居民的健康水平,同时还将会产生关联效应,如通过改善居民的膳食结构而影响餐厨垃圾产量和成份,从而改善深圳的生态环境。请以此为实例场景,基于你们构建的健康水平评估模型设计具有动态监测功能的干预方案;并在充分考虑投入与产出的条件下,结合"健康深圳"行动计划制定实施方案,分析模型的适用性和方案的可行性。
  4. 除了身体健康问题之外,居民的心理应激因素也在日益增加,焦虑、抑郁等常见精神障碍和心理行为问题逐年增多。但公众对精神和心理疾病认识率低,心理健康意识亟待加强。一般认为,居民个体心理健康水平主要取决于保护性因素(如:心理弹性、融洽的家庭与社会关系等)和危险性因素(如:身体疾病、职业倦怠、人际关系困扰等)两方面的相互作用与影响。请在你们构建的健康水平测控模型中增加心理健康评估内容,并尝试对社会公众共性的、与居民个体的心理应激给出综合干预建议方案。
  5. 为了通过"健康深圳"行动计划的实施,让"健康深圳"成为"健康中国"的城市范例,请结合你们的模型分析结果和设计方案,对比国内其他城市相关工作,为"健康深圳"行动计划提出补充或改进建议,尤其是针对健康深圳的信息化建设,在测控数据采集机制的构建与个人隐私保护等方面,也请给出你们的建议。

整体求解过程概述(摘要)

  针对问题一,为了定量刻画深圳居民健康水平的现状与演化趋势,首先对题面、健康深圳行动计划和超重社会负担研究笔记进行数据理解与指标抽取;鉴于附件未给出可直接建模的年度原始数据库,本文将深圳统计公报可核验指标、政策目标、研究笔记观测值及显式标注的目标约束插值构成混合来源面板。通过同向化、量纲消除、极值缩尾和人均化处理,引入熵权法与CRITIC法构建组合权重。结果显示,2014---2018年综合健康指数由0.338提高至0.615;与AHP参考指数的趋势秩相关系数为0.90。在边际收益递减的自然趋势反事实下,2020年目标情景相对反事实产生约0.291的指数增益,表明行动计划在健康素养、环境治理、医疗可及性和健康保障方面存在协同改善空间。
  针对问题二,考虑到"失配性"慢性非传染疾病由行为、环境、医疗和心理因素共同驱动,本文构建健康行为B、健康环境E、医疗服务S、心理保护M、慢病负担C与综合健康H组成的离散系统动力学模型。在此基础上进一步根据附件中深圳成人超重率32.12%、肥胖率9.99%及肥胖与慢病风险关系,生成4200人的机理约束虚拟队列,完成特征工程并比较Logistic、随机森林、梯度提升和RBF-SVM。血脂异常识别的最佳测试AUC为0.776;强化协同政策情景下2030年综合健康状态为0.953,较基准情景0.728提高0.225。这表明城市层面的结构性治理和个体层面的风险分层应当并行实施。
  针对问题三,为了将健康水平评估转化为可执行的动态测控方案,本文选取高血脂人群构建以LDL、脂肪供能比、BMI和餐厨垃圾量为状态,以营养、运动和随访强度为控制量的状态空间模型,并引入模型预测控制最小化健康偏差、环境负担、干预成本与控制波动。24个月仿真中,MPC方案使LDL由3.75 mmol/L降至2.714 mmol/L,估计风险下降16.58%,每10万人净收益约429.3万元,收益成本比为1.25。相较固定强度干预,MPC能够在早期强化干预、后期平滑维持,从而兼顾健康效果、依从性和财政可持续性。
  针对问题四,鉴于心理健康水平取决于保护性因素和危险性因素的动态平衡,本文在个体评估模型中引入压力指数、职业倦怠、慢病、心理弹性、家庭支持、睡眠和医疗可及性,建立"风险---保护"Logit结构并使用四类机器学习模型验证。最佳心理高风险识别AUC为0.745。进一步按照低、中、高、危机四级风险设置普惠科普、组织减压、心理咨询和医疗转介策略,并通过职业---年龄异质性分析确定互联网金融、灵活就业及中年高压群体为重点监测对象。
  针对问题五,基于前述城市综合指数、个体风险模型和动态干预模型,本文对深圳、上海、北京和广州的电子健康档案、跨部门共享、居民查询、家庭医生联动、标准规范和隐私审计等政策特征进行定性编码比较。由此提出"数据采集层---治理交换层---隐私计算层---测控应用层"的健康深圳信息化架构;在数据采集机制方面采用统一主索引、时空标签和最小必要字段,在隐私保护方面采用分级分类、授权目录、联邦学习、差分隐私、全链路审计和退出机制,使城市级政策评估与个体级健康服务之间形成可解释、可追溯的闭环。
  总体而言,本文形成"城市健康综合评价---系统机理分析---个体风险识别---动态优化干预---信息治理保障"的递进建模链条。模型通过权重方法对照、留一指标消融、交叉验证、校准曲线、情景对比和Monte Carlo扰动进行多层验证。需要特别说明的是,附件缺少真实居民级微观数据,虚拟队列与投入产出系数仅用于机理和算法验证;实际部署时应以深圳卫生健康、环境、体育、医保和居民随访的同口径数据重新标定参数。

模型假设:

  假设1:在研究期内,深圳统计公报中人口、收入、床位和卫生技术人员等指标的统计口径总体可比;出现口径调整时,通过人均化和稳健性分析降低影响。
  假设2:政策文本中的2020年目标可被视为决策者期望达到的约束边界,而不是已经实现的观测值。
  假设3:健康水平是行为、环境、医疗、心理、保障和社会经济因素的连续函数,短期内不存在不可逆的突变。
  假设4:虚拟居民队列服从附件提供的超重、肥胖和慢病机制约束,其用途是验证模型结构与算法性能,而非估计真实患病率。
  假设5:个体血脂风险在研究范围内可由年龄、BMI、脂肪供能比、运动、睡眠、吸烟、压力、污染暴露和医疗可及性近似解释。
  假设6:24个月高血脂干预中,各月控制强度在0---1之间,状态方程在基准点附近可线性化,干预效果具有递减性和一定惯性。
  假设7:餐厨垃圾量与高脂、高度加工饮食存在正向关联,健康膳食干预能够同时降低脂肪供能比和部分餐厨垃圾。
  假设8:心理健康风险由危险因素与保护因素共同决定,保护因素可以抵消但不能完全消除强烈危险因素。
  假设9:城市对标矩阵是基于政策文本功能特征的定性编码,不代表城市健康绩效的绝对排名。
  假设10:隐私风险与数据分析效用之间存在连续权衡,采用差分隐私、联邦学习和访问审计能够降低集中汇聚带来的泄露风险。

问题分析:

  问题一问题分析
  针对深圳 2014---2018 年多维度城市健康统计、政策目标混合数据,本题属于多指标综合评价类建模问题,核心目标构建量化综合健康指数,客观评估居民健康演化趋势并测算《健康深圳行动计划》政策增益。难点在于原始数据来源混杂、指标量纲正负不一、缺乏统一标准化口径,单一赋权方法易产生评价偏差;解题思路为先完成同向化、极值缩尾、人均化预处理,融合熵权法与 CRITIC 法构建组合客观权重,搭建年度综合健康评价模型;再构造边际衰减的无政策反事实情景,对比政策目标模拟曲线测算健康指数提升幅度,通过留一指标消融、AHP 权重对照检验模型稳健性,分析经济、医疗、生活行为等维度的结构性健康矛盾。
  问题二问题分析
  针对城市层面慢病系统演化与个体慢病风险分层双重需求,本题融合系统动力学与机器学习分类建模,核心解释 "环境 - 行为 - 医疗 - 心理" 失配慢病形成机理,同步输出城市长期政策仿真与居民个体筛查模型。宏观维度构建包含健康行为、环境、医疗、心理、慢病负担五模块动力学反馈系统,设置基准、强化协同两类政策仿真对比 2030 年健康发展差距;微观维度依托题目给定慢病发病参数生成虚拟居民队列,以 BMI、膳食、压力、污染等为特征,对比 Logistic、随机森林等模型识别血脂异常风险,通过风险分层划分人群并分别给出城市宏观治理、个体健康干预两类配套方案。
  问题三问题分析
  针对高血脂人群长效动态健康管控优化需求,本题属于带多约束模型预测控制(MPC)优化问题,承接问题二个体风险识别结果,将血脂、体重、膳食、餐厨垃圾设为系统状态,营养、运动、随访作为可调干预控制量。核心矛盾为干预健康收益、财政投入、环境损耗、居民依从性多目标相互制衡,固定强度干预易出现前期管控不足或后期资源浪费;建模构建月度离散状态空间方程,设计兼顾长期血脂达标、控制波动、干预成本的综合损失函数,采用数值优化求解 24 个月动态分阶段干预方案,对比无干预、均匀固定干预、MPC 优化三种模式的血脂改善幅度与投入产出比,测算方案净收益与收益成本系数,分析居民依从、干预效力参数对管控效果的敏感性。
  问题四问题分析
  针对居民心理健康量化评估与分层干预设计,本题为风险 - 保护因素二元 Logit 判别建模问题,在问题二居民特征体系基础上新增职业倦怠、心理弹性、家庭支持等心理维度变量。难点是心理健康风险由负面压力、慢病、睡眠等危险因素与运动、医疗等保护因素共同抵消形成,人群存在年龄、职业显著异质性;通过多类机器学习模型对比筛选最优风险识别算法,依据模型输出概率划分低、中、高、危机四级心理风险人群,结合热力图识别互联网金融、灵活就业、中年群体等高风险重点监测对象,匹配科普、减压、心理咨询、医疗转介分级响应干预策略。
  问题五问题分析
  基于前四问城市健康评价、个体风险、动态干预全套模型成果,本题属于公共卫生信息化治理与城市对标优化问题,核心完成北上广深健康信息化政策定性对标,设计兼顾数据分析效用与居民隐私的全流程数据架构。难点是健康医疗数据高度敏感,数据共享、模型运算、个人隐私保护存在天然冲突;先对四座城市健康档案、跨部门共享、隐私审计等政策特征编码对比,识别深圳现有体系短板,搭建 "采集 - 交换 - 隐私计算 - 应用" 四层分层信息系统,引入差分隐私、联邦学习、最小授权机制平衡建模需求与隐私安全,从指标监测、慢病管理、心理服务、数据治理、算法审计多维度给出可落地政策优化补充方案。

模型的建立与求解整体论文缩略图

全部论文请见下方" 只会建模 QQ名片" 点击QQ名片即可

程序代码:(代码和文档not free)

python 复制代码
from __future__ import annotations
import argparse, json, math, os, struct, zipfile, warnings
from pathlib import Path
import numpy as np
import pandas as pd
import scipy.io as sio
from scipy.optimize import milp, LinearConstraint, Bounds
import matplotlib.pyplot as plt
from matplotlib import rcParams, font_manager

warnings.filterwarnings('ignore')
# 强制注册容器内可用的中文字体,避免图表中文显示为方框。
_CN_FONT = Path('/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc')
if _CN_FONT.exists():
    font_manager.fontManager.addfont(str(_CN_FONT))
    _cn_name = font_manager.FontProperties(fname=str(_CN_FONT)).get_name()
    rcParams['font.family'] = _cn_name
    rcParams['font.sans-serif'] = [_cn_name]
else:
    rcParams['font.sans-serif'] = ['Noto Sans CJK SC','SimHei','Microsoft YaHei','Arial Unicode MS','DejaVu Sans']
rcParams['axes.unicode_minus'] = False
rcParams['figure.dpi'] = 140

# --------------------------- OLE/CFB 最小解析器 ---------------------------
FREE=0xFFFFFFFF; END=0xFFFFFFFE
class CFB:
    """只实现读取 OLE Compound File 中普通/迷你流所需的最小功能。"""
    def __init__(self,path:str|Path):
        self.data=Path(path).read_bytes(); h=self.data[:512]
        if h[:8] != bytes.fromhex('D0CF11E0A1B11AE1'):
            raise ValueError('不是 OLE Compound File')
        self.major=struct.unpack_from('<H',h,0x1A)[0]
        self.secsize=1<<struct.unpack_from('<H',h,0x1E)[0]
        self.minisec=1<<struct.unpack_from('<H',h,0x20)[0]
        self.numfat=struct.unpack_from('<I',h,0x2C)[0]
        self.firstdir=struct.unpack_from('<I',h,0x30)[0]
        self.cutoff=struct.unpack_from('<I',h,0x38)[0]
        self.firstminifat=struct.unpack_from('<I',h,0x3C)[0]
        self.numminifat=struct.unpack_from('<I',h,0x40)[0]
        firstdifat=struct.unpack_from('<I',h,0x44)[0]
        numdifat=struct.unpack_from('<I',h,0x48)[0]
        difat=[x for x in struct.unpack_from('<109I',h,0x4C) if x!=FREE]
        sid=firstdifat
        for _ in range(numdifat):
            s=self.sector(sid); vals=list(struct.unpack('<%dI'%(self.secsize//4),s))
            difat += [x for x in vals[:-1] if x!=FREE]; sid=vals[-1]
        self.difat=difat[:self.numfat]
        self.fat=[]
        for sid in self.difat:
            self.fat.extend(struct.unpack('<%dI'%(self.secsize//4),self.sector(sid)))
        dirbytes=self.read_regular(self.firstdir,None)
        self.entries=[]
        for k in range(0,len(dirbytes),128):
            e=dirbytes[k:k+128]
            if len(e)<128: break
            nlen=struct.unpack_from('<H',e,64)[0]
            name=e[:max(0,nlen-2)].decode('utf-16le','ignore') if nlen>=2 else ''
            obj=e[66]; start=struct.unpack_from('<I',e,116)[0]
            size=struct.unpack_from('<Q',e,120)[0] if self.major>=3 else struct.unpack_from('<I',e,120)[0]
            self.entries.append((name,obj,start,size))
        root=next(x for x in self.entries if x[1]==5)
        self.ministream=self.read_regular(root[2],root[3]) if root[2] not in (FREE,END) else b''
        if self.numminifat and self.firstminifat not in (FREE,END):
            mfb=self.read_regular(self.firstminifat,self.numminifat*self.secsize)
            self.minifat=list(struct.unpack('<%dI'%(len(mfb)//4),mfb[:len(mfb)//4*4]))
        else: self.minifat=[]
    def sector(self,sid:int)->bytes:
        off=(sid+1)*self.secsize; return self.data[off:off+self.secsize]
    def chain(self,start:int,fat=None):
        fat=self.fat if fat is None else fat; out=[]; seen=set(); sid=start
        while sid not in (END,FREE) and 0<=sid<len(fat) and sid not in seen:
            seen.add(sid); out.append(sid); sid=fat[sid]
        return out
    def read_regular(self,start:int,size:int|None)->bytes:
        b=b''.join(self.sector(s) for s in self.chain(start))
        return b if size is None else b[:size]
    def read_mini(self,start:int,size:int)->bytes:
        b=b''.join(self.ministream[s*self.minisec:(s+1)*self.minisec] for s in self.chain(start,self.minifat))
        return b[:size]
    def stream(self,name:str)->bytes:
        for n,obj,start,size in self.entries:
            if n==name and obj==2:
                return self.read_mini(start,size) if size<self.cutoff else self.read_regular(start,size)
        raise KeyError(name)

def extract_raw_mat_from_docx(docx_path:Path,out_mat:Path)->Path:
    """从附件 docx 的嵌入 OLE Package 中提取 MATLAB v5 文件。"""
    with zipfile.ZipFile(docx_path) as z:
        embeds=[n for n in z.namelist() if n.startswith('word/embeddings/') and n.endswith('.bin')]
        for n in embeds:
            blob=z.read(n); tmp=out_mat.with_suffix('.ole.tmp'); tmp.write_bytes(blob)
            try:
                c=CFB(tmp)
                native=c.stream('\x01Ole10Native')
                i=native.find(b'MATLAB 5.0 MAT-file')
                if i>=0:
                    # OLE10Native 中 payload 前4字节为嵌入文件长度
                    sz=int.from_bytes(native[i-4:i],'little') if i>=4 else len(native)-i
                    out_mat.write_bytes(native[i:i+sz])
                    tmp.unlink(missing_ok=True)
                    return out_mat
            except Exception:
                pass
            tmp.unlink(missing_ok=True)
    raise FileNotFoundError('未在附件中找到 raw_data.mat')

# --------------------------- 公共工具 ---------------------------
def ensure_dir(p): Path(p).mkdir(parents=True,exist_ok=True); return Path(p)
def wrap_phase(x): return (np.asarray(x)+180)%360-180

def savefig(path,title=None):
    if title: plt.title(title)
    plt.tight_layout(); plt.savefig(path,bbox_inches='tight'); plt.close()

def haversine(lat1,lon1,lat2,lon2):
    R=6371.0
    a1,a2=math.radians(lat1),math.radians(lat2)
    dlat=math.radians(lat2-lat1); dlon=math.radians(lon2-lon1)
    a=math.sin(dlat/2)**2+math.cos(a1)*math.cos(a2)*math.sin(dlon/2)**2
    return 2*R*math.asin(math.sqrt(a))

# --------------------------- 相控阵数据与模型 ---------------------------
class AntennaModel:
    def __init__(self,mat_path:Path,outdir:Path,seed=2026):
        raw=sio.loadmat(mat_path,squeeze_me=True,struct_as_record=False)['raw_data']
        self.AZ=np.asarray(raw.AZ,dtype=float); self.EL=np.asarray(raw.EL,dtype=float)
        self.LogMag=np.asarray(raw.LogMag,dtype=float); self.Phase=np.asarray(raw.Phase,dtype=float)
        self.az=self.AZ[:,0]; self.el=self.EL[0,:]
        self.rng=np.random.default_rng(seed); self.outdir=outdir
        # 数据预处理:非有限值视为不可用测量;dBm功率转为复电场幅度
        finite=np.isfinite(self.LogMag)&np.isfinite(self.Phase)
        amp=np.zeros_like(self.LogMag,float)
        amp[finite]=10**(self.LogMag[finite]/20.0) # sqrt(mW),相对复幅度
        ph=np.deg2rad(np.where(np.isfinite(self.Phase),self.Phase,0.0))
        self.E=amp*np.exp(1j*ph)
        self.valid_channel=np.array([np.isfinite(self.LogMag[:,:,a,:]).mean()>0.95 for a in range(32)])
        self.valid_ants=np.where(self.valid_channel)[0]
        self.dead_ants=np.where(~self.valid_channel)[0]
    def index(self,az,el):
        ia=int(np.argmin(np.abs(self.az-az))); ie=int(np.argmin(np.abs(self.el-el)))
        return ia,ie
    def phase_error_stats(self):
        base=self.Phase[:,:,:,0]
        rows=[]
        for c,nom in [(1,90),(2,180),(3,270)]:
            mask=np.isfinite(base)&np.isfinite(self.Phase[:,:,:,c])&(self.LogMag[:,:,:,0]>-50)&(self.LogMag[:,:,:,c]>-50)
            diff=wrap_phase(self.Phase[:,:,:,c]-base)
            err=wrap_phase(diff-nom)
            vals=err[mask]
            rows.append(dict(config=nom,n=len(vals),mean_error_deg=float(np.mean(vals)),std_deg=float(np.std(vals)),
                             median_abs_error_deg=float(np.median(np.abs(vals))),p95_abs_error_deg=float(np.quantile(np.abs(vals),.95))))
        return pd.DataFrame(rows)
    def align_config(self,az,el,step=3):
        ia,ie=self.index(az,el); Et=self.E[ia,ie,:,:]
        best=(-np.inf,None,None)
        for th in np.arange(-180,180,step):
            cfg=np.full(32,-1,int); s=0j; rot=np.exp(-1j*np.deg2rad(th))
            for a in self.valid_ants:
                vals=Et[a]; j=int(np.argmax(np.real(vals*rot))); cfg[a]=j; s+=vals[j]
            p=abs(s)**2
            if p>best[0]: best=(p,cfg,th)
        return best[1],best[2],10*np.log10(best[0]+1e-30)
    def beam_field(self,cfg,ia=None,ie=None):
        X=self.E if ia is None else self.E[np.ix_(ia,ie,np.arange(32),np.arange(4))]
        out=np.zeros(X.shape[:2],complex)
        for a in np.where(cfg>=0)[0]: out += X[:,:,a,cfg[a]]
        return out
    def micro1_metrics(self,cfg):
        it=self.index(10,5); ii=self.index(10,10)
        ft=self.beam_field(cfg)[it]; fi=self.beam_field(cfg)[ii]
        ia=np.where((self.az>=-60)&(self.az<=60))[0]; ie=np.where((self.el>=-30)&(self.el<=30))[0]
        fr=self.beam_field(cfg,ia,ie); pt=abs(ft)**2+1e-30; pi=abs(fi)**2+1e-30
        AZr,ELr=np.meshgrid(self.az[ia],self.el[ie],indexing='ij')
        side=(np.abs(AZr-10)>10)|(np.abs(ELr-5)>10)
        sll=10*np.log10((np.abs(fr[side])**2+1e-30).max()/pt)
        return dict(target_raw_db=float(10*np.log10(pt)),interference_rel_db=float(10*np.log10(pi/pt)),
                    sll_rel_db=float(sll),n_on=int(np.sum(cfg>=0)))
    def optimize_micro1(self,niter=80000,seeds=6):
        cfg0,theta,_=self.align_config(10,5,step=2)
        ia=np.where((self.az>=-60)&(self.az<=60))[0]; ie=np.where((self.el>=-30)&(self.el<=30))[0]
        R=self.E[np.ix_(ia,ie,np.arange(32),np.arange(4))].reshape(-1,32,4)
        AZr,ELr=np.meshgrid(self.az[ia],self.el[ie],indexing='ij'); side=((np.abs(AZr-10)>10)|(np.abs(ELr-5)>10)).reshape(-1)
        it=self.index(10,5); ii=self.index(10,10); Et=self.E[it[0],it[1],:,:]; Ei=self.E[ii[0],ii[1],:,:]
        def metr(ft,fi,fr,cfg):
            pt=abs(ft)**2+1e-30; pi=abs(fi)**2+1e-30; pr=np.abs(fr)**2+1e-30
            tdb=10*np.log10(pt); ir=10*np.log10(pi/pt); sll=10*np.log10(pr[side].max()/pt)
            # 工程目标:干扰点至少约15dB抑制、旁瓣约8dB以下,同时保留目标增益
            score=tdb-0.45*max(0,ir+15)**2-0.12*max(0,sll+8)**2-0.04*np.sum(cfg<0)
            return score,tdb,ir,sll
        traces=[]; solutions=[]
        options=np.array([-1,0,1,2,3])
        for seed in range(seeds):
            rng=np.random.default_rng(1000+seed); cfg=cfg0.copy(); on=np.where(cfg>=0)[0]
            ft=sum(Et[a,cfg[a]] for a in on); fi=sum(Ei[a,cfg[a]] for a in on)
            fr=sum((R[:,a,cfg[a]] for a in on),np.zeros(R.shape[0],complex))
            score,*mm=metr(ft,fi,fr,cfg); best=(score,cfg.copy(),mm); hist=[]
            for k in range(niter):
                T=1.5*(0.01/1.5)**(k/niter); a=int(rng.choice(self.valid_ants)); old=cfg[a]
                new=int(rng.choice(options[options!=old]));
                oft=Et[a,old] if old>=0 else 0; ntt=Et[a,new] if new>=0 else 0
                ofi=Ei[a,old] if old>=0 else 0; nti=Ei[a,new] if new>=0 else 0
                ofr=R[:,a,old] if old>=0 else 0; ntr=R[:,a,new] if new>=0 else 0
                nft,nfi,nfr=ft-oft+ntt,fi-ofi+nti,fr-ofr+ntr; cfg[a]=new
                ns,*nm=metr(nft,nfi,nfr,cfg); delta=ns-score
                if delta>=0 or rng.random()<math.exp(delta/max(T,1e-12)):
                    ft,fi,fr,score=nft,nfi,nfr,ns
                    if ns>best[0]: best=(ns,cfg.copy(),nm)
                else: cfg[a]=old
                if k%(max(1,niter//200))==0: hist.append(best[0])
            traces.append(hist); solutions.append(best)
        feasible=[s for s in solutions if s[2][1] <= -15 and s[2][2] <= -8]
        best=max(feasible if feasible else solutions,key=lambda s:s[0])
        self.micro1_cfg=best[1]; self.micro1_trace=max(traces,key=lambda h:h[-1]); self.micro1_initial=cfg0
        return cfg0,best[1],pd.DataFrame([self.micro1_metrics(cfg0),self.micro1_metrics(best[1])],index=['目标相位对齐','多目标模拟退火'])
    def micro2_cover(self,threshold=-4.0):
        ia=np.where((self.az>=-30)&(self.az<=30))[0]; ie=np.where((self.el>=-15)&(self.el<=15))[0]
        C=self.E[np.ix_(ia,ie,np.arange(32),np.arange(4))].reshape(-1,32,4)
        points=[(int(self.az[i]),int(self.el[j])) for i in ia for j in ie]
        candidates=[]
        for az,el in points:
            cfg,th,tdb=self.align_config(az,el,step=3); field=np.zeros(C.shape[0],complex)
            for a in np.where(cfg>=0)[0]: field += C[:,a,cfg[a]]
            pdb=10*np.log10(np.abs(field)**2+1e-30); rel=pdb-pdb.max()
            candidates.append(dict(az=az,el=el,cfg=cfg,rel=rel,target_raw_db=tdb))
        cover=np.stack([(c['rel']>=threshold).astype(float) for c in candidates],axis=1)
        cons=LinearConstraint(cover,lb=np.ones(len(points)),ub=np.full(len(points),np.inf))
        res=milp(np.ones(len(candidates)),integrality=np.ones(len(candidates)),bounds=Bounds(0,1),constraints=cons,
                 options={'time_limit':60})
        sel=np.where(res.x>0.5)[0]
        env=np.max(np.stack([candidates[i]['rel'] for i in sel]),axis=0)
        self.micro2=(ia,ie,points,candidates,sel,env)
        return dict(M=int(len(sel)),threshold_db=float(threshold),pit_db=float(env.max()-env.min()),
                    envelope_mean_before_shift_db=float(env.mean()),min_rel_db=float(env.min()),max_rel_db=float(env.max()))
    def robustness_micro1(self,sigmas=(0,0.5,1,2,3,5),trials=300):
        cfg=self.micro1_cfg; it=self.index(10,5); ii=self.index(10,10)
        on=np.where(cfg>=0)[0]; rows=[]
        for s in sigmas:
            ir=[]; tg=[]
            for _ in range(trials):
                e1=self.E[it[0],it[1],on,cfg[on]]*np.exp(1j*np.deg2rad(self.rng.normal(0,s,len(on))))
                e2=self.E[ii[0],ii[1],on,cfg[on]]*np.exp(1j*np.deg2rad(self.rng.normal(0,s,len(on))))
                pt=abs(e1.sum())**2+1e-30; pi=abs(e2.sum())**2+1e-30
                tg.append(10*np.log10(pt)); ir.append(10*np.log10(pi/pt))
            rows.append(dict(sigma_deg=s,target_db_mean=np.mean(tg),target_db_std=np.std(tg),interference_rel_db_mean=np.mean(ir),
                             p_interference_below_minus10=np.mean(np.array(ir)<-10)))
        return pd.DataFrame(rows)
    def make_figures(self):
        fdir=ensure_dir(self.outdir/'figures'); figs=[]
        # 1 缺失率
        miss=1-np.isfinite(self.LogMag).mean(axis=(0,1,3))
        plt.figure(figsize=(8,3.8)); plt.bar(np.arange(1,33),miss*100); plt.xlabel('天线单元编号'); plt.ylabel('功率测量缺失率/%'); plt.grid(axis='y',alpha=.25)
        p=fdir/'fig01_channel_missing.png'; savefig(p,'32个天线单元的数据完整性诊断'); figs.append(p)
        # 2 相位误差箱线
        vals=[]; labels=[]
        base=self.Phase[:,:,:,0]
        for c,nom in [(1,90),(2,180),(3,270)]:
            mask=np.isfinite(base)&np.isfinite(self.Phase[:,:,:,c])&(self.LogMag[:,:,:,0]>-50)&(self.LogMag[:,:,:,c]>-50)
            vals.append(wrap_phase(self.Phase[:,:,:,c]-base-nom)[mask]); labels.append(f'{nom}°')
        plt.figure(figsize=(7,4)); plt.boxplot(vals,labels=labels,showfliers=False); plt.axhline(0,ls='--',lw=1); plt.ylabel('相位偏差/°'); plt.xlabel('名义移相量')
        p=fdir/'fig02_phase_error_box.png'; savefig(p,'移相器实测相位误差分布'); figs.append(p)
        # 3 平均功率热力图
        avg=np.nanmean(np.where(np.isfinite(self.LogMag),self.LogMag,np.nan),axis=(2,3))
        plt.figure(figsize=(8,4)); im=plt.imshow(avg.T,origin='lower',aspect='auto',extent=[self.az.min(),self.az.max(),self.el.min(),self.el.max()]); plt.colorbar(im,label='平均功率/dBm'); plt.xlabel('AZ/°'); plt.ylabel('EL/°')
        p=fdir/'fig03_mean_power_map.png'; savefig(p,'全单元全配置平均辐射功率空间分布'); figs.append(p)
        # 4 目标点单元功率
        it=self.index(10,5); tm=self.LogMag[it[0],it[1],:,:]
        plt.figure(figsize=(9,4));
        for c,label in enumerate(['0°','90°','180°','270°']): plt.plot(np.arange(1,33),tm[:,c],marker='o',ms=3,label=label)
        plt.xlabel('天线单元编号'); plt.ylabel('功率/dBm'); plt.legend(ncol=4); plt.grid(alpha=.2)
        p=fdir/'fig04_target_element_power.png'; savefig(p,'目标方向(AZ=10°, EL=5°)各单元功率观测'); figs.append(p)
        # 5 相位差散点
        mask=np.isfinite(base)&np.isfinite(self.Phase[:,:,:,1])&(self.LogMag[:,:,:,0]>-50)
        x=self.LogMag[:,:,:,0][mask]; y=wrap_phase(self.Phase[:,:,:,1]-base-90)[mask]
        take=np.linspace(0,len(x)-1,min(6000,len(x))).astype(int)
        plt.figure(figsize=(7,4)); plt.scatter(x[take],y[take],s=4,alpha=.25); plt.axhline(np.mean(y),ls='--'); plt.xlabel('0°配置测量功率/dBm'); plt.ylabel('90°配置相对名义值的相位误差/°')
        p=fdir/'fig05_phase_error_vs_power.png'; savefig(p,'相位误差与接收功率的关系'); figs.append(p)
        # 微波1图
        for n,cfg,title in [(6,self.micro1_initial,'目标相位对齐初始波束'),(7,self.micro1_cfg,'多目标优化后的波束')]:
            ia=np.where((self.az>=-60)&(self.az<=60))[0]; ie=np.where((self.el>=-30)&(self.el<=30))[0]
            field=self.beam_field(cfg,ia,ie); pwr=10*np.log10(np.abs(field)**2+1e-30); pwr=pwr-pwr.max()+35
            plt.figure(figsize=(8,4.5)); im=plt.imshow(pwr.T,origin='lower',aspect='auto',extent=[self.az[ia].min(),self.az[ia].max(),self.el[ie].min(),self.el[ie].max()],vmin=10,vmax=35); plt.colorbar(im,label='归一化合成功率/dBm'); plt.scatter([10],[5],marker='*',s=100,label='目标点'); plt.scatter([10],[10],marker='x',s=80,label='保护站点'); plt.xlabel('AZ/°'); plt.ylabel('EL/°'); plt.legend()
            p=fdir/f'fig{n:02d}_micro1_{"initial" if n==6 else "optimized"}.png'; savefig(p,title); figs.append(p)
        # 8/9 AZ/EL切片
        for axis,n in [('AZ',8),('EL',9)]:
            plt.figure(figsize=(8,4));
            for cfg,label in [(self.micro1_initial,'目标相位对齐'),(self.micro1_cfg,'多目标优化')]:
                F=self.beam_field(cfg); P=10*np.log10(np.abs(F)**2+1e-30); P=P-P.max()+35
                if axis=='AZ':
                    ie=self.index(10,5)[1]; plt.plot(self.az,P[:,ie],label=label); plt.xlim(-60,60); plt.xlabel('AZ/°')
                else:
                    ia=self.index(10,5)[0]; plt.plot(self.el,P[ia,:],label=label); plt.xlim(-30,30); plt.xlabel('EL/°'); plt.axvline(10,ls='--',lw=1,label='保护站点EL=10°')
            plt.ylabel('归一化合成功率/dBm'); plt.legend(); plt.grid(alpha=.25)
            p=fdir/f'fig{n:02d}_micro1_{axis.lower()}_cut.png'; savefig(p,f'微波问题1:{axis}方向功率切片对比'); figs.append(p)
        # 10 收敛曲线
        plt.figure(figsize=(7,4)); plt.plot(np.linspace(0,1,len(self.micro1_trace)),self.micro1_trace); plt.xlabel('归一化迭代进度'); plt.ylabel('当前最优目标函数值'); plt.grid(alpha=.25)
        p=fdir/'fig10_sa_convergence.png'; savefig(p,'模拟退火多目标优化收敛过程'); figs.append(p)
        # 11 微波1方法指标
        rows=[]
        for cfg,label in [(self.micro1_initial,'相位对齐'),(self.micro1_cfg,'多目标优化')]:
            m=self.micro1_metrics(cfg); rows.append([label,m['target_raw_db'],m['interference_rel_db'],m['sll_rel_db']])
        df=pd.DataFrame(rows,columns=['方法','目标点原始合成dB','干扰相对dB','旁瓣相对dB'])
        x=np.arange(2); w=.25; plt.figure(figsize=(7,4));
        for k,col in enumerate(df.columns[1:]): plt.bar(x+(k-1)*w,df[col],width=w,label=col)
        plt.xticks(x,df['方法']); plt.ylabel('dB'); plt.legend(fontsize=8); plt.axhline(0,lw=.6)
        p=fdir/'fig11_micro1_method_compare.png'; savefig(p,'微波问题1不同求解策略的性能比较'); figs.append(p)
        # 12 鲁棒性
        rob=self.robustness_micro1(); self.robust_df=rob
        plt.figure(figsize=(7,4)); plt.errorbar(rob.sigma_deg,rob.target_db_mean,yerr=rob.target_db_std,marker='o',capsize=3,label='目标点合成功率'); plt.plot(rob.sigma_deg,rob.interference_rel_db_mean,marker='s',label='干扰相对功率'); plt.xlabel('附加相位扰动标准差/°'); plt.ylabel('dB'); plt.legend(); plt.grid(alpha=.25)
        p=fdir/'fig12_micro1_robustness.png'; savefig(p,'波束配置对相位测量误差的鲁棒性'); figs.append(p)
        # 微波2
        ia,ie,points,cands,sel,env=self.micro2
        plt.figure(figsize=(7,4)); counts=[np.sum(c['rel']>=-4) for c in cands]; plt.hist(counts,bins=np.arange(1,max(counts)+2)-.5,rwidth=.85); plt.xlabel('单个候选波束覆盖的5°网格点数(-4dB阈值)'); plt.ylabel('候选波束数')
        p=fdir/'fig13_candidate_coverage_hist.png'; savefig(p,'候选波束覆盖能力分布'); figs.append(p)
        plt.figure(figsize=(7,4)); plt.scatter([cands[i]['az'] for i in sel],[cands[i]['el'] for i in sel],s=60); plt.xlim(-33,33); plt.ylim(-18,18); plt.xticks(np.arange(-30,31,10)); plt.yticks(np.arange(-15,16,5)); plt.grid(alpha=.25); plt.xlabel('AZ/°'); plt.ylabel('EL/°')
        p=fdir/'fig14_selected_beam_centers.png'; savefig(p,f'最小集合覆盖选择的{len(sel)}个波束中心'); figs.append(p)
        E=env.reshape(len(ia),len(ie)); shifted=E-E.mean()+35
        plt.figure(figsize=(8,4)); im=plt.imshow(shifted.T,origin='lower',aspect='auto',extent=[-30,30,-15,15]); plt.colorbar(im,label='扫描覆盖包络/dBm'); plt.xlabel('AZ/°'); plt.ylabel('EL/°')
        p=fdir/'fig15_coverage_envelope.png'; savefig(p,'多波束扫描覆盖功率包络(均值归一化到35dBm)'); figs.append(p)
        Ms=[]; pits=[]; ths=np.arange(-2.5,-6.1,-.5)
        for th in ths:
            cover=np.stack([(c['rel']>=th).astype(float) for c in cands],axis=1); cons=LinearConstraint(cover,np.ones(len(points)),np.full(len(points),np.inf))
            r=milp(np.ones(len(cands)),integrality=np.ones(len(cands)),bounds=Bounds(0,1),constraints=cons,options={'time_limit':15})
            if r.x is not None:
                ss=np.where(r.x>.5)[0]; ee=np.max(np.stack([cands[i]['rel'] for i in ss]),axis=0); Ms.append(len(ss)); pits.append(ee.max()-ee.min())
            else: Ms.append(np.nan); pits.append(np.nan)
        self.threshold_df=pd.DataFrame({'threshold_db':ths,'M':Ms,'pit_db':pits})
        plt.figure(figsize=(7,4)); plt.plot(-ths,Ms,marker='o'); plt.xlabel('允许凹坑阈值绝对值/dB'); plt.ylabel('最少波束数M'); plt.grid(alpha=.25)
        p=fdir/'fig16_beam_number_tradeoff.png'; savefig(p,'覆盖均匀性与最少波束数量的Pareto权衡'); figs.append(p)
        return figs
    def phase_vector_text(self,cfg):
        vals=[]
        for x in cfg: vals.append('OFF' if x<0 else str([0,90,180,270][x]))
        return vals

# --------------------------- 骨干网模型 ---------------------------
class BackboneModel:
    def __init__(self,outdir:Path):
        self.outdir=outdir
        # 2018年常住人口采用广东统计年鉴量级并作四舍五入;收入为建模所用可替换基准(元/人年)
        data=[
('广州',23.129,113.264,1490,60000,0.020),('深圳',22.543,114.058,1303,57500,0.028),('珠海',22.271,113.576,189,52500,0.025),
('汕头',23.354,116.682,566,27000,0.007),('佛山',23.021,113.122,790,50000,0.018),('韶关',24.811,113.597,299,26000,0.000),
('河源',23.746,114.700,309,24000,0.003),('梅州',24.289,116.122,438,24500,-0.002),('惠州',23.111,114.416,483,36000,0.014),
('汕尾',22.786,115.375,300,23000,0.004),('东莞',23.020,113.752,839,49000,0.017),('中山',22.517,113.392,331,46000,0.014),
('江门',22.579,113.082,459,33000,0.005),('阳江',21.858,111.982,255,26000,0.002),('湛江',21.271,110.359,733,25000,0.006),
('茂名',21.663,110.925,631,25000,0.003),('肇庆',23.047,112.465,415,30000,0.006),('清远',23.682,113.056,387,28000,0.009),
('潮州',23.657,116.622,266,25500,0.001),('揭阳',23.550,116.372,609,24500,0.003),('云浮',22.915,112.044,253,25000,-0.001)]
        self.city=pd.DataFrame(data,columns=['city','lat','lon','pop2018_10k','income2018','pop_growth'])
        gz=self.city[self.city.city=='广州'].iloc[0]
        self.city['geo_km']=self.city.apply(lambda r:haversine(gz.lat,gz.lon,r.lat,r.lon),axis=1)
        self.city['route_km']=self.city.geo_km*1.15
        self.tech=pd.DataFrame([('400G',200,32,1.00),('600G',100,48,1.25),('800G',80,64,1.50)],columns=['format','reach_km','capacity_tbps','cost_X'])
    def demand_forecast(self):
        years=np.arange(2018,2029); rows=[]
        # 人均高峰等效带宽:2018=0.65 Mbps,技术渗透与视频/云业务驱动 CAGR 22%
        # 收入弹性0.22,年龄结构在省级层面折算为平均乘子1.0
        for _,r in self.city.iterrows():
            for y in years:
                pop=r.pop2018_10k*1e4*((1+r.pop_growth)**(y-2018))
                income=r.income2018*(1.065**(y-2018))
                percap=0.65*(1.22**(y-2018))*((income/40000)**0.22)
                demand_tbps=pop*percap/1e6
                rows.append((r.city,y,pop/1e4,income,percap,demand_tbps))
        self.forecast=pd.DataFrame(rows,columns=['city','year','population_10k','income','percap_peak_mbps','demand_tbps'])
        d28=self.forecast[self.forecast.year==2028][['city','population_10k','income','demand_tbps']]
        self.city=self.city.merge(d28,on='city',suffixes=('','_2028'))
        return self.forecast
    def full_deployment(self):
        rows=[]
        for _,r in self.city[self.city.city!='广州'].iterrows():
            best=None
            for _,t in self.tech.iterrows():
                seg=max(1,math.ceil(r.route_km/t.reach_km)); systems=max(1,math.ceil(r.demand_tbps/t.capacity_tbps))
                cap=systems*t.capacity_tbps; cost=seg*systems*t.cost_X
                # 价值函数:满足全量需求前提下最小标准化建设成本,同时轻度奖励容量余量
                utility=1000-10*cost+0.03*(cap-r.demand_tbps)
                rec=dict(city=r.city,format=t['format'],segments=seg,systems=systems,capacity_tbps=cap,cost_X=cost,demand_tbps=r.demand_tbps,utility=utility,route_km=r.route_km)
                if best is None or rec['cost_X']<best['cost_X']-1e-9 or (abs(rec['cost_X']-best['cost_X'])<1e-9 and rec['capacity_tbps']<best['capacity_tbps']): best=rec
            rows.append(best)
        self.full=pd.DataFrame(rows)
        return self.full
    def selective_value_model(self,budget_ratio=.72):
        # 12类人群:4年龄×3收入;人群价值 = 接入人口×服务价值权重×数字需求强度
        ages=['0-17','18-34','35-59','60+']; age_share=np.array([.18,.27,.38,.17]); age_dem=np.array([.75,1.45,1.15,.55]); age_val=np.array([.75,1.30,1.10,.80])
        incs=['低收入','中收入','高收入']; inc_share=np.array([.35,.45,.20]); inc_dem=np.array([.75,1.0,1.35]); inc_val=np.array([.70,1.0,1.45])
        cities=self.city[self.city.city!='广州'].reset_index(drop=True)
        options=[]
        # 每城市允许0建设或各格式1~4套(超过需求上限也不需要)
        for ci,r in cities.iterrows():
            opts=[dict(city_i=ci,city=r.city,format='不建设',capacity=0,cost=0)]
            for _,t in self.tech.iterrows():
                seg=max(1,math.ceil(r.route_km/t.reach_km))
                maxsys=max(1,math.ceil(r.demand_tbps/t.capacity_tbps)+1)
                for n in range(1,maxsys+1): opts.append(dict(city_i=ci,city=r.city,format=t['format'],capacity=n*t.capacity_tbps,cost=seg*n*t.cost_X))
            options.extend(opts)
        opts=pd.DataFrame(options); groups=[]
        for ci,r in cities.iterrows():
            base=r.demand_tbps
            for ai,a in enumerate(ages):
                for ji,inc in enumerate(incs):
                    share=age_share[ai]*inc_share[ji]
                    raw=share*age_dem[ai]*inc_dem[ji]
                    # 归一化确保各人群需求和约等于城市总需求
                    groups.append(dict(city_i=ci,city=r.city,age=a,income_group=inc,share=share,raw=raw,
                                       value_weight=age_val[ai]*inc_val[ji]*(r.income/40000)**0.12))
        groups=pd.DataFrame(groups)
        for ci in range(len(cities)):
            ix=groups.city_i==ci; groups.loc[ix,'demand_tbps']=cities.loc[ci,'demand_tbps']*groups.loc[ix,'raw']/groups.loc[ix,'raw'].sum()
            groups.loc[ix,'population_10k']=cities.loc[ci,'population_10k']*groups.loc[ix,'share']
            groups.loc[ix,'value']=groups.loc[ix,'population_10k']*groups.loc[ix,'value_weight']
        nO=len(opts); nG=len(groups); n=nO+nG
        c=np.r_[np.zeros(nO),-groups.value.values]
        integrality=np.ones(n); lb=np.zeros(n); ub=np.ones(n)
        A=[]; L=[]; U=[]
        # 每城市恰选一个部署选项
        for ci in range(len(cities)):
            row=np.zeros(n); row[np.where(opts.city_i.values==ci)[0]]=1; A.append(row); L.append(1); U.append(1)
        # 城市容量:sum group demand*x <= sum option capacity*y
        for ci in range(len(cities)):
            row=np.zeros(n); oi=np.where(opts.city_i.values==ci)[0]; gi=np.where(groups.city_i.values==ci)[0]
            row[oi]=-opts.loc[oi,'capacity'].values; row[nO+gi]=groups.loc[gi,'demand_tbps'].values
            A.append(row); L.append(-np.inf); U.append(0)
        fullcost=float(self.full.cost_X.sum()); budget=budget_ratio*fullcost
        row=np.zeros(n); row[:nO]=opts.cost.values; A.append(row); L.append(-np.inf); U.append(budget)
        res=milp(c,integrality=integrality,bounds=Bounds(lb,ub),constraints=LinearConstraint(np.array(A),np.array(L),np.array(U)),options={'time_limit':60})
        y=res.x[:nO]>0.5; x=res.x[nO:]>0.5
        self.select_opts=opts[y].copy(); self.select_groups=groups[x].copy(); self.groups=groups; self.budget=budget
        return dict(budget_ratio=budget_ratio,budget_X=budget,selected_cost_X=float(self.select_opts.cost.sum()),
                    value=float(self.select_groups.value.sum()),population_covered_10k=float(self.select_groups.population_10k.sum()),
                    total_population_10k=float(groups.groupby('city_i').population_10k.sum().sum()),n_cities_buil
全部论文请见下方" 只会建模 QQ名片" 点击QQ名片即可
相关推荐
一只小小的土拨鼠3 小时前
2026 华数杯备考与决胜攻略:基于“数据清洗-特征归因-混合算法-多目标优化”的电动汽车营销精准决策全流程方案
数学建模
数模竞赛Paid answer3 小时前
2023年五一杯数学建模A题无人机定点投放问题求解全过程完整文档及程序
算法·数学建模·无人机·五一杯
数模竞赛Paid answer19 小时前
2025年五一杯数学建模A题支路车流量推测问题求解全过程论文及程序
数学建模·数据分析·五一杯
Tasiorh21 小时前
数学建模预测模型汇总
数学建模
cui_ruicheng1 天前
Python数据分析(十四):Pandas 数据可视化
python·信息可视化·数据分析·pandas
leisoo80971 天前
筹码分布数据分析实战:用Python构建主力建仓成本分析系统
python·数据挖掘·数据分析
编程风暴1 天前
零基础数据分析项目模板+5条避坑红线
java·数据挖掘·数据分析
观远数据1 天前
Excel、自研、还是换BI?产品VP拆解三条数据分析路线的隐性成本
大数据·数据分析·excel
隔窗听雨眠2 天前
AI重塑数据分析:从SQL执行者到智能基础设施构建者
人工智能·sql·数据分析