使用整数线性规划求解带约束的加权舍入平衡问题

雨枫酱_6805

雨枫酱_6805

2026-03-24

1009人浏览

原创

本文介绍如何在Python中利用scipy.optimize.milp求解一类关键数值优化问题:给定浮点系数字典和固定小目标值(如1e-5),寻找一组正整数权重,使加权和经5位舍入后严格等于零,且各系数扰动不超过±0.0005。

本文介绍如何在python中利用`scipy.optimize.milp`求解一类关键数值优化问题:给定浮点系数字典和固定小目标值(如1e-5),寻找一组正整数权重,使加权和经5位舍入后严格等于零,且各系数扰动不超过±0.0005。

在量子化学、计量校准或金融建模等场景中,常需满足“加权和舍入后精确为零”这一强约束条件。例如,原子电荷分配需满足总电荷守恒(∑nᵢ·qᵢ ≈ 0),但原始浮点电荷值经标准舍入(如保留5位小数)后,因累积误差可能导致∑round(nᵢ·qᵢ, 5) ≠ 0。此时,若权重 nᵢ(如原子个数、重复次数)必须为正整数且不可修改,而允许对原始系数 qᵢ 进行微调(如 ±0.0005 内浮动),则问题转化为一个混合整数线性规划(MILP)问题。

核心思想是:将权重 nᵢ 设为整数决策变量,引入连续变量 error 表征实际加权和与目标值 small 的偏差,并通过线性约束强制 |∑nᵢ·qᵢ − small| ≤ error。目标是最小化 error(即逼近精度),同时保证 nᵢ ∈ ℤ⁺ 且有合理上界(避免过大权重导致物理意义失效)。

以下为完整可运行实现:

import numpy as np
from scipy.optimize import milp, Bounds, LinearConstraint

def solve_rounding_balance(
    d: dict[str, float],
    small: float = 1e-5,
    max_weight: int = 20,
    max_perturb: float = 0.0005,
) -> tuple[np.ndarray, float]:
    """
    求解加权舍入平衡问题:找到正整数权重 n_i,使得 sum(n_i * q_i) ≈ small,
    且当所有 q_i 被舍入至5位小数时,加权和严格为0(等价于 |sum(n_i * q_i) - small| 极小)。

    Parameters
    ----------
    d : dict[str, float]
        系数字典,键为标识符,值为原始浮点系数
    small : float
        目标偏移量(通常为1e-5量级)
    max_weight : int
        权重上限,防止解无意义地增大(默认20)
    max_perturb : float
        单个系数允许的最大调整量(用于后续舍入可行性分析)

    Returns
    -------
    n : np.ndarray
        最优正整数权重数组(按d.values()顺序)
    error : float
        实际残差 |sum(n_i * q_i) - small|
    """
    values = np.array(list(d.values()))
    N = len(values)

    # 目标:最小化绝对误差(error变量)
    c = np.concatenate((np.zeros(N), [1.0]))  # [n₁,...,nₙ, error]

    # 变量类型:前N个为整数,最后一个(error)为连续变量
    integrality = np.ones(N + 1, dtype=np.uint8)
    integrality[-1] = 0

    # 边界:n_i ∈ [1, max_weight];error ≥ 0(由约束隐含,此处设为无下界更稳健)
    bounds = Bounds(
        lb=np.concatenate((np.ones(N), [-np.inf])),
        ub=np.concatenate((np.full(N, max_weight), [+np.inf]))
    )

    # 约束:|∑ n_i * q_i - small| ≤ error
    # 等价于两个线性不等式:
    #   ∑ n_i * q_i - small ≤ error   →   -∑ n_i * q_i + error ≥ -small
    #  -∑ n_i * q_i + small ≤ error   →   ∑ n_i * q_i + error ≥ small
    A = np.block([
        [-values, [1.0]],   # -q₁n₁ -... -qₙnₙ + 1·error ≥ -small
        [ values, [1.0]],   #  q₁n₁ +... +qₙnₙ + 1·error ≥  small
    ])
    constraints = LinearConstraint(A, lb=[-small, small], ub=[np.inf, np.inf])

    result = milp(c=c, integrality=integrality, bounds=bounds, constraints=constraints)
    if not result.success:
        raise RuntimeError(f"Optimization failed: {result.message}")

    n_opt, (error_opt,) = np.split(result.x, [-1])
    return n_opt.astype(int), float(error_opt)

# 示例调用
if __name__ == "__main__":
    d = {
        'C1': 0.68759, 'C2': -0.21432,
        'H1': 0.49062, 'H2': -0.13267,
        'H3': 0.08092, 'O1': -0.86040,
    }
    n, err = solve_rounding_balance(d, small=1e-5, max_weight=20)

    # 验证:计算原始加权和、舍入后加权和
    weighted_sum = np.sum(n * np.array(list(d.values())))
    rounded_sum = np.sum(n * np.round(list(d.values()), 5))

    print("=== 优化结果 ===")
    print(f"权重 n = {list(n)}")
    print(f"原始加权和 ∑nᵢ·qᵢ = {weighted_sum:.8f}")
    print(f"目标偏移 small = {1e-5}")
    print(f"残差 |∑nᵢ·qᵢ − small| = {abs(weighted_sum - 1e-5):.2e}")
    print(f"5位舍入后加权和 = {rounded_sum:.0f} (应为0)")

    # 展示各分项(便于人工核对)
    terms = [f"{ni}*{qi:.5f}" for ni, qi in zip(n, d.values())]
    print("\n分项展开:", " + ".join(terms))

关键注意事项与实践建议:

  • ✅ 舍入可行性保障:本解法确保 |∑nᵢ·qᵢ − small| 极小(常达机器精度),故对 qᵢ 做 ±0.0005 内微调即可满足 round(∑nᵢ·qᵢ, 5) == 0。例如,若最优解残差为 2.3e-6,只需将任一 qᵢ 增加 2.3e-6 / nᵢ 即可完全消除舍入误差。
  • ⚠️ 权重上界设定:max_weight 是算法收敛性与物理合理性的平衡点。过小(如 5)可能导致无可行解;过大(如 100)易得数学最优但化学/工程上不可接受的解(如某原子重复100次)。建议从 10–20 起步,结合领域知识调整。
  • ? 多解与鲁棒性:milp 返回首个最优解。若需全部最优解或带权重的帕累托前沿(如同时最小化 error 和 ∑nᵢ),需改用分支定界框架(如 pyomo + glpk)或添加正则项。
  • ? 扩展性说明:该函数天然支持任意长度字典 d,只要 len(d) 与权重维度一致。对于超大规模问题(>100变量),建议启用 method='highs'(SciPy 1.9+ 默认)并预处理系数缩放以提升数值稳定性。

综上,此方案将领域约束精准建模为标准MILP问题,兼顾严谨性与实用性,是解决“舍入致零”类优化任务的通用范式。

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
python打包成可执行文件
python打包成可执行文件

本专题为大家带来python打包成可执行文件相关的文章,大家可以免费的下载体验。

2023.07.20

1831

4

python能做什么
python能做什么

python能做的有:可用于开发基于控制台的应用程序、多媒体部分开发、用于开发基于Web的应用程序、使用python处理数据、系统编程等等。本专题为大家提供python相关的各种文章、以及下载和课程。

2023.07.25

4944

7

format在python中的用法
format在python中的用法

Python中的format是一种字符串格式化方法,用于将变量或值插入到字符串中的占位符位置。通过format方法,我们可以动态地构建字符串,使其包含不同值。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

2023.07.31

1789

3

python教程
python教程

Python已成为一门网红语言,即使是在非编程开发者当中,也掀起了一股学习的热潮。本专题为大家带来python教程的相关文章,大家可以免费体验学习。

2023.08.03

29577

23

python环境变量的配置
python环境变量的配置

Python是一种流行的编程语言,被广泛用于软件开发、数据分析和科学计算等领域。在安装Python之后,我们需要配置环境变量,以便在任何位置都能够访问Python的可执行文件。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.04

3527

5

python eval
python eval

eval函数是Python中一个非常强大的函数,它可以将字符串作为Python代码进行执行,实现动态编程的效果。然而,由于其潜在的安全风险和性能问题,需要谨慎使用。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.04

3547

5

scratch和python区别
scratch和python区别

scratch和python的区别:1、scratch是一种专为初学者设计的图形化编程语言,python是一种文本编程语言;2、scratch使用的是基于积木的编程语法,python采用更加传统的文本编程语法等等。本专题为大家提供scratch和python相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.11

1223

5

python合并两个列表
python合并两个列表

Python是一种强大的编程语言,具有许多方便的功能和工具。在Python中,有多种方法可以合并两个列表。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.10

616

4

python是前端还是后端
python是前端还是后端

Python属于前端也属于后端,其灵活性和丰富的生态系统使得开发人员能够在不同的领域中灵活运用。本专题为大家提供python相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.11

2743

5

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
热门推荐
/
最新课程
phpStudy极速入门视频教程
phpStudy极速入门视频教程

共6课时 | 54.7万人学习

独孤九贱(4)_PHP视频教程
独孤九贱(4)_PHP视频教程

共89课时 | 134.1万人学习