Pyomo基础学习笔记【3】:将模型的组成要素结构化报告
1. 结构化的输出表达式
希望在完整报告中不包含 expr 列(即原始表达式字符串),可以简单地从报告生成函数中移除该列。生成不包含 expr 列的完整报告。
函数代码
点击查看代码
# pyomo_utils/reporting.py
import pandas as pd
import pyomo.environ as pyo
def generate_expression_report(model, include_expr=False):
    """
    生成包含所有表达式详细信息的结构化报告,支持索引表达式和标量表达式
    
    参数:
        model: Pyomo模型对象
        include_expr: 是否包含原始表达式字符串,默认为 False
        
    返回:
        pandas.DataFrame 包含以下列:
        - component: 表达式组件名称
        - doc: 文档字符串/显示名称
        - type: 类型(Indexed/Scalar)
        - index: 索引值(仅索引表达式)
        - value: 表达式值
        - expr: 原始表达式字符串(如果 include_expr=True)
    """
    report = []
    
    for expr_obj in model.component_objects(pyo.Expression, descend_into=True):
        # 基础信息
        base_info = {
            'component': expr_obj.name,
            'doc': getattr(expr_obj, 'doc', None),
            'type': 'Indexed' if expr_obj.is_indexed() else 'Scalar'
        }
        
        if expr_obj.is_indexed():
            # 处理索引表达式
            for idx in expr_obj.index_set():
                try:
                    expr_data = expr_obj[idx]
                    entry = {
                        **base_info,
                        'index': str(idx),
                        'value': pyo.value(expr_data)
                    }
                    if include_expr:
                        entry['expr'] = str(expr_data.expr)
                    report.append(entry)
                except Exception as e:
                    entry = {
                        **base_info,
                        'index': str(idx),
                        'value': f"Error: {str(e)}"
                    }
                    if include_expr:
                        entry['expr'] = "N/A"
                    report.append(entry)
        else:
            # 处理标量表达式
            try:
                entry = {
                    **base_info,
                    'index': None,
                    'value': pyo.value(expr_obj)
                }
                if include_expr:
                    entry['expr'] = str(expr_obj.expr)
                report.append(entry)
            except Exception as e:
                entry = {
                    **base_info,
                    'index': None,
                    'value': f"Error: {str(e)}"
                }
                if include_expr:
                    entry['expr'] = "N/A"
                report.append(entry)
    
    return pd.DataFrame(report)
使用示例
在主脚本中,可以这样使用这个包:
# main.py
from pyomo_utils import generate_expression_report
from pyomo.environ import ConcreteModel, Var, Expression, Param
# 创建测试模型
m = ConcreteModel()
# 添加标量表达式
m.scalar_expr = Expression(expr=1.5, doc="标量测试表达式")
# 添加索引表达式
m.index_set = [1, 2, 3]
m.param = Param(m.index_set, initialize={1: 10, 2: 20, 3: 30})
m.indexed_expr = Expression(m.index_set, 
                          rule=lambda m, i: m.param[i] * 2,
                          doc="索引测试表达式")
# 生成报告,不包含 expr 列
report_df = generate_expression_report(m, include_expr=False)
# 打印Markdown格式报告
print("## 表达式完整报告 (不包含 expr 列)")
print(report_df.to_markdown(index=False))
# 生成报告,包含 expr 列
report_df_with_expr = generate_expression_report(m, include_expr=True)
# 打印Markdown格式报告
print("\n## 表达式完整报告 (包含 expr 列)")
print(report_df_with_expr.to_markdown(index=False))
输出示例
运行 main.py 后,你将看到类似以下的输出:
## 表达式完整报告 (不包含 expr 列)
| component    | doc                | type    | index   | value   |
|:-------------|:-------------------|:--------|:--------|:--------|
| scalar_expr  | 标量测试表达式     | Scalar  | None    | 1.5     |
| indexed_expr | 索引测试表达式     | Indexed | 1       | 20.0    |
| indexed_expr | 索引测试表达式     | Indexed | 2       | 40.0    |
| indexed_expr | 索引测试表达式     | Indexed | 3       | 60.0    |
## 表达式完整报告 (包含 expr 列)
| component    | doc                | type    | index   | value   | expr              |
|:-------------|:-------------------|:--------|:--------|:--------|:------------------|
| scalar_expr  | 标量测试表达式     | Scalar  | None    | 1.5     | 1.5               |
| indexed_expr | 索引测试表达式     | Indexed | 1       | 20.0    | param[1] * 2      |
| indexed_expr | 索引测试表达式     | Indexed | 2       | 40.0    | param[2] * 2      |
| indexed_expr | 索引测试表达式     | Indexed | 3       | 60.0    | param[3] * 2      |
详细解释
- 
component- 表达式的名称。
 - 示例:
scalar_expr、indexed_expr。 
 - 
doc- 表达式的文档字符串或描述。
 - 示例:
标量测试表达式、索引测试表达式。 
 - 
type- 表达式的类型。
 - 示例:
Scalar、Indexed。 Scalar:标量表达式,没有索引。Indexed:索引表达式,有多个值,每个索引对应一个值。
 - 
index- 索引值(仅索引表达式)。
 - 示例:
1、2、3。 - 对于标量表达式,
index为None。 
 - 
value- 表达式的值。
 - 示例:
1.5、20.0、40.0、60.0。 - 如果计算表达式的值时发生错误,会显示错误信息。
 
 - 
expr(可选)- 原始表达式字符串。
 - 示例:
1.5、param[1] * 2。 - 如果 
include_expr=False,则不包含此列。 
 
2. 变量和参数报告生成函数
好的,我们可以仿照表达式报告的写法,分别编写针对变量和参数的报告生成函数。这些函数将生成包含变量和参数详细信息的结构化报告,支持索引变量/参数和标量变量/参数。
1. 变量报告生成函数
为了在变量报告中增加变量的上下限信息,可以在生成变量报告的函数中添加 lower_bound 和 upper_bound 列。以下是修改后的变量报告生成函数:
# pyomo_utils/reporting.py
import pandas as pd
import pyomo.environ as pyo
def generate_variable_report(model, include_expr=False):
    """
    生成包含所有变量详细信息的结构化报告,支持索引变量和标量变量
    
    参数:
        model: Pyomo模型对象
        include_expr: 是否包含原始表达式字符串,默认为 False
        
    返回:
        pandas.DataFrame 包含以下列:
        - component: 变量组件名称
        - doc: 文档字符串/显示名称
        - type: 类型(Indexed/Scalar)
        - index: 索引值(仅索引变量)
        - value: 变量值
        - lower_bound: 变量下限
        - upper_bound: 变量上限
        - expr: 原始表达式字符串(如果 include_expr=True)
    """
    report = []
    
    for var_obj in model.component_objects(pyo.Var, descend_into=True):
        # 基础信息
        base_info = {
            'component': var_obj.name,
            'doc': getattr(var_obj, 'doc', None),
            'type': 'Indexed' if var_obj.is_indexed() else 'Scalar'
        }
        
        if var_obj.is_indexed():
            # 处理索引变量
            for idx in var_obj.index_set():
                try:
                    var_data = var_obj[idx]
                    entry = {
                        **base_info,
                        'index': str(idx),
                        'value': pyo.value(var_data),
                        'lower_bound': var_data.lb,
                        'upper_bound': var_data.ub
                    }
                    if include_expr:
                        entry['expr'] = str(var_data)
                    report.append(entry)
                except Exception as e:
                    entry = {
                        **base_info,
                        'index': str(idx),
                        'value': f"Error: {str(e)}",
                        'lower_bound': f"Error: {str(e)}",
                        'upper_bound': f"Error: {str(e)}"
                    }
                    if include_expr:
                        entry['expr'] = "N/A"
                    report.append(entry)
        else:
            # 处理标量变量
            try:
                entry = {
                    **base_info,
                    'index': None,
                    'value': pyo.value(var_obj),
                    'lower_bound': var_obj.lb,
                    'upper_bound': var_obj.ub
                }
                if include_expr:
                    entry['expr'] = str(var_obj)
                report.append(entry)
            except Exception as e:
                entry = {
                    **base_info,
                    'index': None,
                    'value': f"Error: {str(e)}",
                    'lower_bound': f"Error: {str(e)}",
                    'upper_bound': f"Error: {str(e)}"
                }
                if include_expr:
                    entry['expr'] = "N/A"
                report.append(entry)
    
    return pd.DataFrame(report)
2. 参数报告生成函数
# pyomo_utils/reporting.py
def generate_parameter_report(model, include_expr=False):
    """
    生成包含所有参数详细信息的结构化报告,支持索引参数和标量参数
    
    参数:
        model: Pyomo模型对象
        include_expr: 是否包含原始表达式字符串,默认为 False
        
    返回:
        pandas.DataFrame 包含以下列:
        - component: 参数组件名称
        - doc: 文档字符串/显示名称
        - type: 类型(Indexed/Scalar)
        - index: 索引值(仅索引参数)
        - value: 参数值
        - expr: 原始表达式字符串(如果 include_expr=True)
    """
    report = []
    
    for param_obj in model.component_objects(pyo.Param, descend_into=True):
        # 基础信息
        base_info = {
            'component': param_obj.name,
            'doc': getattr(param_obj, 'doc', None),
            'type': 'Indexed' if param_obj.is_indexed() else 'Scalar'
        }
        
        if param_obj.is_indexed():
            # 处理索引参数
            for idx in param_obj.index_set():
                try:
                    param_data = param_obj[idx]
                    entry = {
                        **base_info,
                        'index': str(idx),
                        'value': pyo.value(param_data)
                    }
                    if include_expr:
                        entry['expr'] = str(param_data)
                    report.append(entry)
                except Exception as e:
                    entry = {
                        **base_info,
                        'index': str(idx),
                        'value': f"Error: {str(e)}"
                    }
                    if include_expr:
                        entry['expr'] = "N/A"
                    report.append(entry)
        else:
            # 处理标量参数
            try:
                entry = {
                    **base_info,
                    'index': None,
                    'value': pyo.value(param_obj)
                }
                if include_expr:
                    entry['expr'] = str(param_obj)
                report.append(entry)
            except Exception as e:
                entry = {
                    **base_info,
                    'index': None,
                    'value': f"Error: {str(e)}"
                }
                if include_expr:
                    entry['expr'] = "N/A"
                report.append(entry)
    
    return pd.DataFrame(report)
使用示例
在主脚本中,可以这样使用这些函数:
# main.py
from pyomo_utils import generate_variable_report, generate_parameter_report
from pyomo.environ import ConcreteModel, Var, Param
# 创建测试模型
m = ConcreteModel()
# 添加标量变量和索引变量
m.x = Var(initialize=1.5, doc="标量测试变量", bounds=(0, 5))
m.index_set = [1, 2, 3]
m.y = Var(m.index_set, initialize={1: 10, 2: 20, 3: 30}, doc="索引测试变量", bounds=(0, 100))
# 添加标量参数和索引参数
m.a = Param(initialize=5, doc="标量测试参数")
m.b = Param(m.index_set, initialize={1: 100, 2: 200, 3: 300}, doc="索引测试参数")
# 生成变量报告,不包含 expr 列
var_report_df = generate_variable_report(m, include_expr=False)
# 打印Markdown格式报告
print("## 变量完整报告 (不包含 expr 列)")
print(var_report_df.to_markdown(index=False))
# 生成参数报告,不包含 expr 列
param_report_df = generate_parameter_report(m, include_expr=False)
# 打印Markdown格式报告
print("\n## 参数完整报告 (不包含 expr 列)")
print(param_report_df.to_markdown(index=False))
输出示例
运行 main.py 后,你将看到类似以下的输出:
## 变量完整报告 (不包含 expr 列)
| component    | doc                | type    | index   | value   | lower_bound   | upper_bound   |
|:-------------|:-------------------|:--------|:--------|:--------|:--------------|:--------------|
| x            | 标量测试变量       | Scalar  | None    | 1.5     | 0.0           | 5.0           |
| y            | 索引测试变量       | Indexed | 1       | 10.0    | 0.0           | 100.0         |
| y            | 索引测试变量       | Indexed | 2       | 20.0    | 0.0           | 100.0         |
| y            | 索引测试变量       | Indexed | 3       | 30.0    | 0.0           | 100.0         |
## 参数完整报告 (不包含 expr 列)
| component    | doc                | type    | index   | value   |
|:-------------|:-------------------|:--------|:--------|:--------|
| a            | 标量测试参数       | Scalar  | None    | 5       |
| b            | 索引测试参数       | Indexed | 1       | 100     |
| b            | 索引测试参数       | Indexed | 2       | 200     |
| b            | 索引测试参数       | Indexed | 3       | 300     |
详细解释
- 
component- 变量或参数的名称。
 - 示例:
x、y、a、b。 
 - 
doc- 变量或参数的文档字符串或描述。
 - 示例:
标量测试变量、索引测试变量、标量测试参数、索引测试参数。 
 - 
type- 变量或参数的类型。
 - 示例:
Scalar、Indexed。 Scalar:标量变量或参数,没有索引。Indexed:索引变量或参数,有多个值,每个索引对应一个值。
 - 
index- 索引值(仅索引变量/参数)。
 - 示例:
1、2、3。 - 对于标量变量/参数,
index为None。 
 - 
value- 变量或参数的值。
 - 示例:
1.5、10.0、20.0、30.0、5、100、200、300。 
 - 
lower_bound- 变量的下限。
 - 示例:0.0。
 
 - 
upper_bound- 变量的上限。
 - 示例:5.0、100.0。
 
 - 
expr(可选)- 原始变量表达式字符串。
 - 如果 include_expr=True,则包含此列。
 
 
3. 目标函数报告生成函数
为了生成目标函数的报告,我们可以编写一个函数来收集模型中所有目标函数的相关信息。以下是一个示例代码,展示如何生成目标函数的报告:
目标函数报告生成函数
# pyomo_utils/reporting.py
import pandas as pd
import pyomo.environ as pyo
def generate_objective_report(model, include_expr=False):
    """
    生成包含所有目标函数详细信息的结构化报告
    
    参数:
        model: Pyomo模型对象
        include_expr: 是否包含原始表达式字符串,默认为 False
        
    返回:
        pandas.DataFrame 包含以下列:
        - component: 目标函数组件名称
        - doc: 文档字符串/显示名称
        - sense: 目标函数类型(minimize/maximize)
        - value: 目标函数值
        - expr: 原始表达式字符串(如果 include_expr=True)
    """
    report = []
    
    for obj_obj in model.component_objects(pyo.Objective, descend_into=True):
        # 基础信息
        base_info = {
            'component': obj_obj.name,
            'doc': getattr(obj_obj, 'doc', None),
            'sense': 'minimize' if obj_obj.sense == pyo.minimize else 'maximize'
        }
        
        try:
            entry = {
                **base_info,
                'value': pyo.value(obj_obj)
            }
            if include_expr:
                entry['expr'] = str(obj_obj.expr)
            report.append(entry)
        except Exception as e:
            entry = {
                **base_info,
                'value': f"Error: {str(e)}"
            }
            if include_expr:
                entry['expr'] = "N/A"
            report.append(entry)
    
    return pd.DataFrame(report)
使用示例
在主脚本中,可以这样使用这个函数:
# main.py
from pyomo_utils import generate_objective_report
from pyomo.environ import ConcreteModel, Var, Objective
# 创建测试模型
m = ConcreteModel()
# 添加变量
m.x = Var(initialize=1.5, doc="测试变量")
# 添加目标函数
m.obj1 = Objective(expr=m.x**2, sense=minimize, doc="最小化目标函数")
m.obj2 = Objective(expr=-m.x**2, sense=maximize, doc="最大化目标函数")
# 生成目标函数报告,不包含 expr 列
obj_report_df = generate_objective_report(m, include_expr=False)
# 打印Markdown格式报告
print("## 目标函数完整报告 (不包含 expr 列)")
print(obj_report_df.to_markdown(index=False))
输出示例
运行 main.py 后,你将看到类似以下的输出:
## 目标函数完整报告 (不包含 expr 列)
| component    | doc               | sense      | value   |
|:-------------|:-------------------|:-----------|:--------|
| obj1         | 最小化目标函数     | minimize   | 2.25    |
| obj2         | 最大化目标函数     | maximize   | -2.25   |
详细解释
- 
component- 目标函数的名称。
 - 示例:
obj1、obj2。 
 - 
doc- 目标函数的文档字符串或描述。
 - 示例:
最小化目标函数、最大化目标函数。 
 - 
sense- 目标函数的类型。
 - 示例:
minimize、maximize。 
 - 
value- 目标函数的值。
 - 示例:
2.25、-2.25。 
 - 
expr(可选)- 原始目标函数表达式字符串。
 - 如果 
include_expr=True,则包含此列。 
 
通过这种方式,你可以生成一个包含目标函数详细信息的结构化报告,帮助你更好地理解和调试模型中的目标函数。
4. 完整代码
点击查看代码
# --coding:utf-8--
# pyomo_utils/reporting.py
import pandas as pd
import pyomo.environ as pyo
def generate_expression_report(model, include_expr=False):
    """
    生成包含所有表达式详细信息的结构化报告,支持索引表达式和标量表达式
    参数:
        model: Pyomo模型对象
        include_expr: 是否包含原始表达式字符串,默认为 False
    返回:
        pandas.DataFrame 包含以下列:
        - component: 表达式组件名称
        - doc: 文档字符串/显示名称
        - type: 类型(Indexed/Scalar)
        - index: 索引值(仅索引表达式)
        - value: 表达式值
        - expr: 原始表达式字符串(如果 include_expr=True)
    """
    report = []
    for expr_obj in model.component_objects(pyo.Expression, descend_into=True):
        # 基础信息
        base_info = {
            'component': expr_obj.name,
            'doc': getattr(expr_obj, 'doc', None),
            'type': 'Indexed' if expr_obj.is_indexed() else 'Scalar'
        }
        if expr_obj.is_indexed():
            # 处理索引表达式
            for idx in expr_obj.index_set():
                try:
                    expr_data = expr_obj[idx]
                    entry = {
                        **base_info,
                        'index': str(idx),
                        'value': pyo.value(expr_data)
                    }
                    if include_expr:
                        entry['expr'] = str(expr_data.expr)
                    report.append(entry)
                except Exception as e:
                    entry = {
                        **base_info,
                        'index': str(idx),
                        'value': f"Error: {str(e)}"
                    }
                    if include_expr:
                        entry['expr'] = "N/A"
                    report.append(entry)
        else:
            # 处理标量表达式
            try:
                entry = {
                    **base_info,
                    'index': None,
                    'value': pyo.value(expr_obj)
                }
                if include_expr:
                    entry['expr'] = str(expr_obj.expr)
                report.append(entry)
            except Exception as e:
                entry = {
                    **base_info,
                    'index': None,
                    'value': f"Error: {str(e)}"
                }
                if include_expr:
                    entry['expr'] = "N/A"
                report.append(entry)
    return pd.DataFrame(report)
def generate_variable_report(model, include_expr=False):
    """
    生成包含所有变量详细信息的结构化报告,支持索引变量和标量变量
    参数:
        model: Pyomo模型对象
        include_expr: 是否包含原始表达式字符串,默认为 False
    返回:
        pandas.DataFrame 包含以下列:
        - component: 变量组件名称
        - doc: 文档字符串/显示名称
        - type: 类型(Indexed/Scalar)
        - index: 索引值(仅索引变量)
        - value: 变量值
        - lower_bound: 变量下限
        - upper_bound: 变量上限
        - expr: 原始表达式字符串(如果 include_expr=True)
    """
    report = []
    for var_obj in model.component_objects(pyo.Var, descend_into=True):
        # 基础信息
        base_info = {
            'component': var_obj.name,
            'doc': getattr(var_obj, 'doc', None),
            'type': 'Indexed' if var_obj.is_indexed() else 'Scalar'
        }
        if var_obj.is_indexed():
            # 处理索引变量
            for idx in var_obj.index_set():
                try:
                    var_data = var_obj[idx]
                    entry = {
                        **base_info,
                        'index': str(idx),
                        'value': pyo.value(var_data),
                        'lower_bound': var_data.lb,
                        'upper_bound': var_data.ub
                    }
                    if include_expr:
                        entry['expr'] = str(var_data)
                    report.append(entry)
                except Exception as e:
                    entry = {
                        **base_info,
                        'index': str(idx),
                        'value': f"Error: {str(e)}",
                        'lower_bound': f"Error: {str(e)}",
                        'upper_bound': f"Error: {str(e)}"
                    }
                    if include_expr:
                        entry['expr'] = "N/A"
                    report.append(entry)
        else:
            # 处理标量变量
            try:
                entry = {
                    **base_info,
                    'index': None,
                    'value': pyo.value(var_obj),
                    'lower_bound': var_obj.lb,
                    'upper_bound': var_obj.ub
                }
                if include_expr:
                    entry['expr'] = str(var_obj)
                report.append(entry)
            except Exception as e:
                entry = {
                    **base_info,
                    'index': None,
                    'value': f"Error: {str(e)}",
                    'lower_bound': f"Error: {str(e)}",
                    'upper_bound': f"Error: {str(e)}"
                }
                if include_expr:
                    entry['expr'] = "N/A"
                report.append(entry)
    return pd.DataFrame(report)
def generate_parameter_report(model, include_expr=False):
    """
    生成包含所有参数详细信息的结构化报告,支持索引参数和标量参数
    参数:
        model: Pyomo模型对象
        include_expr: 是否包含原始表达式字符串,默认为 False
    返回:
        pandas.DataFrame 包含以下列:
        - component: 参数组件名称
        - doc: 文档字符串/显示名称
        - type: 类型(Indexed/Scalar)
        - index: 索引值(仅索引参数)
        - value: 参数值
        - expr: 原始表达式字符串(如果 include_expr=True)
    """
    report = []
    for param_obj in model.component_objects(pyo.Param, descend_into=True):
        # 基础信息
        base_info = {
            'component': param_obj.name,
            'doc': getattr(param_obj, 'doc', None),
            'type': 'Indexed' if param_obj.is_indexed() else 'Scalar'
        }
        if param_obj.is_indexed():
            # 处理索引参数
            for idx in param_obj.index_set():
                try:
                    param_data = param_obj[idx]
                    entry = {
                        **base_info,
                        'index': str(idx),
                        'value': pyo.value(param_data)
                    }
                    if include_expr:
                        entry['expr'] = str(param_data)
                    report.append(entry)
                except Exception as e:
                    entry = {
                        **base_info,
                        'index': str(idx),
                        'value': f"Error: {str(e)}"
                    }
                    if include_expr:
                        entry['expr'] = "N/A"
                    report.append(entry)
        else:
            # 处理标量参数
            try:
                entry = {
                    **base_info,
                    'index': None,
                    'value': pyo.value(param_obj)
                }
                if include_expr:
                    entry['expr'] = str(param_obj)
                report.append(entry)
            except Exception as e:
                entry = {
                    **base_info,
                    'index': None,
                    'value': f"Error: {str(e)}"
                }
                if include_expr:
                    entry['expr'] = "N/A"
                report.append(entry)
    return pd.DataFrame(report)
def generate_constraint_report(model, include_expr=False):
    """
    生成包含所有约束详细信息的结构化报告,支持索引约束和标量约束
    参数:
        model: Pyomo模型对象
        include_expr: 是否包含原始表达式字符串,默认为 False
    返回:
        pandas.DataFrame 包含以下列:
        - component: 约束组件名称
        - doc: 文档字符串/显示名称
        - type: 类型(Indexed/Scalar)
        - constraint_type: 约束类型(Inequality/Equality)
        - index: 索引值(仅索引约束)
        - lower: 约束下界
        - body: 约束表达式值
        - upper: 约束上界
        - expr: 原始表达式字符串(如果 include_expr=True)
    """
    report = []
    for constr_obj in model.component_objects(pyo.Constraint, descend_into=True):
        # 基础信息
        base_info = {
            'component': constr_obj.name,
            'doc': getattr(constr_obj, 'doc', None),
            'type': 'Indexed' if constr_obj.is_indexed() else 'Scalar'
        }
        if constr_obj.is_indexed():
            # 处理索引约束
            for idx in constr_obj.index_set():
                try:
                    constr_data = constr_obj[idx]
                    # 判断约束类型
                    if constr_data.lower is not None and constr_data.upper is not None and constr_data.lower == constr_data.upper:
                        constr_type = 'Equality'
                    else:
                        constr_type = 'Inequality'
                    entry = {
                        **base_info,
                        'constraint_type': constr_type,
                        'index': str(idx),
                        'lower': pyo.value(constr_data.lower) if constr_data.lower is not None else '-inf',
                        'body': pyo.value(constr_data.body),
                        'upper': pyo.value(constr_data.upper) if constr_data.upper is not None else 'inf'
                    }
                    if include_expr:
                        entry['expr'] = str(constr_data.expr)
                    report.append(entry)
                except Exception as e:
                    entry = {
                        **base_info,
                        'constraint_type': 'Unknown',
                        'index': str(idx),
                        'lower': f"Error: {str(e)}",
                        'body': f"Error: {str(e)}",
                        'upper': f"Error: {str(e)}"
                    }
                    if include_expr:
                        entry['expr'] = "N/A"
                    report.append(entry)
        else:
            # 处理标量约束
            try:
                # 判断约束类型
                if constr_obj.lower is not None and constr_obj.upper is not None and constr_obj.lower == constr_obj.upper:
                    constr_type = 'Equality'
                else:
                    constr_type = 'Inequality'
                entry = {
                    **base_info,
                    'constraint_type': constr_type,
                    'index': None,
                    'lower': pyo.value(constr_obj.lower) if constr_obj.lower is not None else '-inf',
                    'body': pyo.value(constr_obj.body),
                    'upper': pyo.value(constr_obj.upper) if constr_obj.upper is not None else 'inf'
                }
                if include_expr:
                    entry['expr'] = str(constr_obj.expr)
                report.append(entry)
            except Exception as e:
                entry = {
                    **base_info,
                    'constraint_type': 'Unknown',
                    'index': None,
                    'lower': f"Error: {str(e)}",
                    'body': f"Error: {str(e)}",
                    'upper': f"Error: {str(e)}"
                }
                if include_expr:
                    entry['expr'] = "N/A"
                report.append(entry)
    return pd.DataFrame(report)
def generate_objective_report(model, include_expr=False):
    """
    生成包含所有目标函数详细信息的结构化报告
    参数:
        model: Pyomo模型对象
        include_expr: 是否包含原始表达式字符串,默认为 False
    返回:
        pandas.DataFrame 包含以下列:
        - component: 目标函数组件名称
        - doc: 文档字符串/显示名称
        - sense: 目标函数类型(minimize/maximize)
        - value: 目标函数值
        - expr: 原始表达式字符串(如果 include_expr=True)
    """
    report = []
    for obj_obj in model.component_objects(pyo.Objective, descend_into=True):
        # 基础信息
        base_info = {
            'component': obj_obj.name,
            'doc': getattr(obj_obj, 'doc', None),
            'sense': 'minimize' if obj_obj.sense == pyo.minimize else 'maximize'
        }
        try:
            entry = {
                **base_info,
                'value': pyo.value(obj_obj)
            }
            if include_expr:
                entry['expr'] = str(obj_obj.expr)
            report.append(entry)
        except Exception as e:
            entry = {
                **base_info,
                'value': f"Error: {str(e)}"
            }
            if include_expr:
                entry['expr'] = "N/A"
            report.append(entry)
    return pd.DataFrame(report)
# 使用示例
if __name__ == "__main__":
    # 创建测试模型
    from pyomo.environ import ConcreteModel, Var, Param,Expression,Constraint,Objective,minimize,maximize
    m = ConcreteModel()
    # 添加标量变量和索引变量
    m.x = Var(initialize=1.5, doc="标量测试变量")
    m.index_set = [1, 2, 3]
    m.y = Var(m.index_set, initialize={1: 10, 2: 20, 3: 30}, doc="索引测试变量")
    # 添加标量参数和索引参数
    m.a = Param(initialize=5, doc="标量测试参数")
    m.b = Param(m.index_set, initialize={1: 100, 2: 200, 3: 300}, doc="索引测试参数")
    # 添加标量表达式
    m.scalar_expr = Expression(expr=1.5, doc="标量测试表达式")
    # 添加索引表达式
    m.index_set = [1, 2, 3]
    m.param = Param(m.index_set, initialize={1: 10, 2: 20, 3: 30}, doc="索引测试参数")
    m.indexed_expr = Expression(m.index_set,
                              rule=lambda m, i: m.param[i] * 2,
                              doc="索引测试表达式")
    # 添加标量不等式约束
    m.scalar_ineq_constr = Constraint(expr=m.x >= 1, doc="标量不等式约束")
    # 添加标量等式约束
    m.scalar_eq_constr = Constraint(expr=m.x == 2, doc="标量等式约束")
    # 添加索引约束
    m.index_set = [1, 2, 3]
    m.indexed_ineq_constr = Constraint(m.index_set,
                                       rule=lambda m, i: m.x >= i * 0.5,
                                       doc="索引不等式约束")
    m.indexed_eq_constr = Constraint(m.index_set,
                                     rule=lambda m, i: m.x == i * 0.5 + 0.5,
                                     doc="索引等式约束")
    # 添加目标函数
    m.obj1 = Objective(expr=m.x ** 2, sense=minimize, doc="最小化目标函数")
    m.obj2 = Objective(expr=-m.x ** 2, sense=maximize, doc="最大化目标函数")
    # 生成表达式报告,不包含 expr 列
    report_df = generate_expression_report(m, include_expr=False)
    # 打印Markdown格式报告
    print("## 表达式完整报告 (不包含 expr 列)")
    print(report_df.to_markdown(index=False))
    # # 生成报告,包含 expr 列
    # report_df_with_expr = generate_expression_report(m, include_expr=True)
    #
    # # 打印Markdown格式报告
    # print("\n## 表达式完整报告 (包含 expr 列)")
    # print(report_df_with_expr.to_markdown(index=False))
    # 生成变量报告,不包含 expr 列
    var_report_df = generate_variable_report(m, include_expr=False)
    # 打印Markdown格式报告
    print("\n## 变量完整报告 (不包含 expr 列)")
    print(var_report_df.to_markdown(index=False))
    # 生成参数报告,不包含 expr 列
    param_report_df = generate_parameter_report(m, include_expr=False)
    # 打印Markdown格式报告
    print("\n## 参数完整报告 (不包含 expr 列)")
    print(param_report_df.to_markdown(index=False))
    # 生成约束报告,不包含 expr 列
    constr_report_df = generate_constraint_report(m, include_expr=False)
    # 打印Markdown格式报告
    print("\n## 约束完整报告 (不包含 expr 列)")
    print(constr_report_df.to_markdown(index=False))
    # 生成目标函数报告,不包含 expr 列
    obj_report_df = generate_objective_report(m, include_expr=False)
    # 打印Markdown格式报告
    print("## 目标函数完整报告 (不包含 expr 列)")
    print(obj_report_df.to_markdown(index=False))
5. 功能扩展
添加灵敏度分析:可以扩展函数,为约束添加松弛值和对偶值分析
增加格式化选项:添加更多输出格式选项,如 Excel、HTML 等
添加可视化功能:集成 matplotlib 等库,为报表提供可视化支持
增加筛选功能:允许用户按组件名称、类型等条件筛选报表
根据扩展建议,对原有工具进行了增强,添加了灵敏度分析、多种输出格式支持、可视化功能和筛选功能。
扩展功能说明
我已将原有的函数整合到一个面向对象的框架中,并增加了以下功能:
- 
灵敏度分析:
- 新增了
generate_sensitivity_report方法,用于分析约束的对偶值和影子价格 - 扩展了约束报表,增加了松弛值和约束激活状态的计算
 
 - 新增了
 - 
多种输出格式支持:
- 新增了
export_report方法,支持CSV、Excel、HTML和JSON等格式导出 - 支持批量导出所有类型的报表到指定目录
 
 - 新增了
 - 
可视化功能:
- 新增了三个可视化方法:
visualize_variables:可视化变量值visualize_constraints:可视化约束松弛值visualize_objectives:可视化目标函数值
 - 所有可视化都支持保存到文件或直接显示
 
 - 新增了三个可视化方法:
 - 
筛选功能:
- 为所有报表生成方法添加了
filter_func参数,允许用户自定义筛选条件 - 可以根据组件名称、类型等条件筛选报表内容
 
 - 为所有报表生成方法添加了
 - 
完整报告生成:
- 新增了
generate_full_report方法,可生成包含所有报表和可视化的HTML或Markdown格式报告 - 报告包含摘要信息和各组件的详细分析
 
 - 新增了
 - 
增强的数据处理:
- 增加了变量类型识别(二进制、整数等)
 - 增加了参数可变性(mutable)的识别
 - 增加了目标函数的活跃状态标识
 
 
使用方法
扩展后的工具使用更加灵活,用户可以:
- 
创建报表生成器实例:
reporter = PyomoModelReporter(model, name="模型分析") - 
生成特定类型的报表:
var_report = reporter.generate_variable_report(include_expr=True) - 
筛选报表内容:
# 筛选名称包含"cost"的约束 cost_constr = reporter.generate_constraint_report( filter_func=lambda c: "cost" in c.name.lower() ) - 
生成灵敏度分析:
sensitivity = reporter.generate_sensitivity_report() - 
可视化数据:
reporter.visualize_variables(output_path="variables.png") - 
导出所有报表:
reporter.export_report(format="excel", directory="./reports") - 
生成完整报告:
reporter.generate_full_report(format="html", include_sensitivity=True) 
这些扩展使工具更加全面,能够满足更复杂的模型分析需求,同时保持了原有功能的易用性。
扩展代码
不是很完善,AI生成的。
点击查看代码
# --coding:utf-8--
# pyomo_utils/reporting.py
import pandas as pd
import pyomo.environ as pyo
import matplotlib.pyplot as plt
import seaborn as sns
from typing import List, Dict, Optional, Union, Callable
import os
from datetime import datetime
class PyomoModelReporter:
    """Pyomo模型报表生成器,提供完整的模型分析和可视化功能"""
    
    def __init__(self, model: pyo.ConcreteModel, name: Optional[str] = None):
        """
        初始化报表生成器
        
        参数:
            model: Pyomo模型对象
            name: 报表名称,默认为当前时间戳
        """
        self.model = model
        self.name = name or f"Pyomo_Model_Report_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
        self.results = {}  # 存储生成的报表结果
        self.sensitivities = {}  # 存储灵敏度分析结果
        
    def generate_expression_report(self, include_expr: bool = False, filter_func: Optional[Callable] = None) -> pd.DataFrame:
        """
        生成包含所有表达式详细信息的结构化报告
        
        参数:
            include_expr: 是否包含原始表达式字符串
            filter_func: 筛选函数,接受组件对象,返回布尔值
            
        返回:
            pandas.DataFrame 包含表达式信息的报表
        """
        report = []
        for expr_obj in self.model.component_objects(pyo.Expression, descend_into=True):
            # 应用筛选函数
            if filter_func and not filter_func(expr_obj):
                continue
                
            # 基础信息
            base_info = {
                'component': expr_obj.name,
                'doc': getattr(expr_obj, 'doc', None),
                'type': 'Indexed' if expr_obj.is_indexed() else 'Scalar'
            }
            if expr_obj.is_indexed():
                # 处理索引表达式
                for idx in expr_obj.index_set():
                    try:
                        expr_data = expr_obj[idx]
                        entry = {
                            **base_info,
                            'index': str(idx),
                            'value': pyo.value(expr_data)
                        }
                        if include_expr:
                            entry['expr'] = str(expr_data.expr)
                        report.append(entry)
                    except Exception as e:
                        entry = {
                            **base_info,
                            'index': str(idx),
                            'value': f"Error: {str(e)}"
                        }
                        if include_expr:
                            entry['expr'] = "N/A"
                        report.append(entry)
            else:
                # 处理标量表达式
                try:
                    entry = {
                        **base_info,
                        'index': None,
                        'value': pyo.value(expr_obj)
                    }
                    if include_expr:
                        entry['expr'] = str(expr_obj.expr)
                    report.append(entry)
                except Exception as e:
                    entry = {
                        **base_info,
                        'index': None,
                        'value': f"Error: {str(e)}"
                    }
                    if include_expr:
                        entry['expr'] = "N/A"
                    report.append(entry)
        df = pd.DataFrame(report)
        self.results['expressions'] = df
        return df
    def generate_variable_report(self, include_expr: bool = False, filter_func: Optional[Callable] = None) -> pd.DataFrame:
        """
        生成包含所有变量详细信息的结构化报告
        
        参数:
            include_expr: 是否包含原始表达式字符串
            filter_func: 筛选函数,接受组件对象,返回布尔值
            
        返回:
            pandas.DataFrame 包含变量信息的报表
        """
        report = []
        for var_obj in self.model.component_objects(pyo.Var, descend_into=True):
            # 应用筛选函数
            if filter_func and not filter_func(var_obj):
                continue
                
            # 基础信息
            base_info = {
                'component': var_obj.name,
                'doc': getattr(var_obj, 'doc', None),
                'type': 'Indexed' if var_obj.is_indexed() else 'Scalar'
            }
            if var_obj.is_indexed():
                # 处理索引变量
                for idx in var_obj.index_set():
                    try:
                        var_data = var_obj[idx]
                        entry = {
                            **base_info,
                            'index': str(idx),
                            'value': pyo.value(var_data),
                            'lower_bound': var_data.lb,
                            'upper_bound': var_data.ub,
                            'is_fixed': var_data.fixed,
                            'is_binary': var_data.domain is pyo.Binary,
                            'is_integer': var_data.domain is pyo.Integers
                        }
                        if include_expr:
                            entry['expr'] = str(var_data)
                        report.append(entry)
                    except Exception as e:
                        entry = {
                            **base_info,
                            'index': str(idx),
                            'value': f"Error: {str(e)}",
                            'lower_bound': f"Error: {str(e)}",
                            'upper_bound': f"Error: {str(e)}",
                            'is_fixed': False,
                            'is_binary': False,
                            'is_integer': False
                        }
                        if include_expr:
                            entry['expr'] = "N/A"
                        report.append(entry)
            else:
                # 处理标量变量
                try:
                    entry = {
                        **base_info,
                        'index': None,
                        'value': pyo.value(var_obj),
                        'lower_bound': var_obj.lb,
                        'upper_bound': var_obj.ub,
                        'is_fixed': var_obj.fixed,
                        'is_binary': var_obj.domain is pyo.Binary,
                        'is_integer': var_obj.domain is pyo.Integers
                    }
                    if include_expr:
                        entry['expr'] = str(var_obj)
                    report.append(entry)
                except Exception as e:
                    entry = {
                        **base_info,
                        'index': None,
                        'value': f"Error: {str(e)}",
                        'lower_bound': f"Error: {str(e)}",
                        'upper_bound': f"Error: {str(e)}",
                        'is_fixed': False,
                        'is_binary': False,
                        'is_integer': False
                    }
                    if include_expr:
                        entry['expr'] = "N/A"
                    report.append(entry)
        df = pd.DataFrame(report)
        self.results['variables'] = df
        return df
    def generate_parameter_report(self, include_expr: bool = False, filter_func: Optional[Callable] = None) -> pd.DataFrame:
        """
        生成包含所有参数详细信息的结构化报告
        
        参数:
            include_expr: 是否包含原始表达式字符串
            filter_func: 筛选函数,接受组件对象,返回布尔值
            
        返回:
            pandas.DataFrame 包含参数信息的报表
        """
        report = []
        for param_obj in self.model.component_objects(pyo.Param, descend_into=True):
            # 应用筛选函数
            if filter_func and not filter_func(param_obj):
                continue
                
            # 基础信息
            base_info = {
                'component': param_obj.name,
                'doc': getattr(param_obj, 'doc', None),
                'type': 'Indexed' if param_obj.is_indexed() else 'Scalar'
            }
            if param_obj.is_indexed():
                # 处理索引参数
                for idx in param_obj.index_set():
                    try:
                        param_data = param_obj[idx]
                        entry = {
                            **base_info,
                            'index': str(idx),
                            'value': pyo.value(param_data),
                            'mutable': param_obj.mutable
                        }
                        if include_expr:
                            entry['expr'] = str(param_data)
                        report.append(entry)
                    except Exception as e:
                        entry = {
                            **base_info,
                            'index': str(idx),
                            'value': f"Error: {str(e)}",
                            'mutable': False
                        }
                        if include_expr:
                            entry['expr'] = "N/A"
                        report.append(entry)
            else:
                # 处理标量参数
                try:
                    entry = {
                        **base_info,
                        'index': None,
                        'value': pyo.value(param_obj),
                        'mutable': param_obj.mutable
                    }
                    if include_expr:
                        entry['expr'] = str(param_obj)
                    report.append(entry)
                except Exception as e:
                    entry = {
                        **base_info,
                        'index': None,
                        'value': f"Error: {str(e)}",
                        'mutable': False
                    }
                    if include_expr:
                        entry['expr'] = "N/A"
                    report.append(entry)
        df = pd.DataFrame(report)
        self.results['parameters'] = df
        return df
    def generate_constraint_report(self, include_expr: bool = False, filter_func: Optional[Callable] = None) -> pd.DataFrame:
        """
        生成包含所有约束详细信息的结构化报告
        
        参数:
            include_expr: 是否包含原始表达式字符串
            filter_func: 筛选函数,接受组件对象,返回布尔值
            
        返回:
            pandas.DataFrame 包含约束信息的报表
        """
        report = []
        for constr_obj in self.model.component_objects(pyo.Constraint, descend_into=True):
            # 应用筛选函数
            if filter_func and not filter_func(constr_obj):
                continue
                
            # 基础信息
            base_info = {
                'component': constr_obj.name,
                'doc': getattr(constr_obj, 'doc', None),
                'type': 'Indexed' if constr_obj.is_indexed() else 'Scalar'
            }
            if constr_obj.is_indexed():
                # 处理索引约束
                for idx in constr_obj.index_set():
                    try:
                        constr_data = constr_obj[idx]
                        # 判断约束类型
                        if constr_data.lower is not None and constr_data.upper is not None and constr_data.lower == constr_data.upper:
                            constr_type = 'Equality'
                        else:
                            constr_type = 'Inequality'
                        # 计算松弛值
                        slack = None
                        if constr_type == 'Equality':
                            slack = abs(pyo.value(constr_data.body) - pyo.value(constr_data.lower))
                        else:
                            if constr_data.lower is not None:
                                slack = pyo.value(constr_data.body) - pyo.value(constr_data.lower)
                            elif constr_data.upper is not None:
                                slack = pyo.value(constr_data.upper) - pyo.value(constr_data.body)
                        entry = {
                            **base_info,
                            'constraint_type': constr_type,
                            'index': str(idx),
                            'lower': pyo.value(constr_data.lower) if constr_data.lower is not None else '-inf',
                            'body': pyo.value(constr_data.body),
                            'upper': pyo.value(constr_data.upper) if constr_data.upper is not None else 'inf',
                            'slack': slack,
                            'is_active': slack == 0 if slack is not None else False
                        }
                        if include_expr:
                            entry['expr'] = str(constr_data.expr)
                        report.append(entry)
                    except Exception as e:
                        entry = {
                            **base_info,
                            'constraint_type': 'Unknown',
                            'index': str(idx),
                            'lower': f"Error: {str(e)}",
                            'body': f"Error: {str(e)}",
                            'upper': f"Error: {str(e)}",
                            'slack': None,
                            'is_active': False
                        }
                        if include_expr:
                            entry['expr'] = "N/A"
                        report.append(entry)
            else:
                # 处理标量约束
                try:
                    # 判断约束类型
                    if constr_obj.lower is not None and constr_obj.upper is not None and constr_obj.lower == constr_obj.upper:
                        constr_type = 'Equality'
                    else:
                        constr_type = 'Inequality'
                    # 计算松弛值
                    slack = None
                    if constr_type == 'Equality':
                        slack = abs(pyo.value(constr_obj.body) - pyo.value(constr_obj.lower))
                    else:
                        if constr_obj.lower is not None:
                            slack = pyo.value(constr_obj.body) - pyo.value(constr_obj.lower)
                        elif constr_obj.upper is not None:
                            slack = pyo.value(constr_obj.upper) - pyo.value(constr_obj.body)
                    entry = {
                        **base_info,
                        'constraint_type': constr_type,
                        'index': None,
                        'lower': pyo.value(constr_obj.lower) if constr_obj.lower is not None else '-inf',
                        'body': pyo.value(constr_obj.body),
                        'upper': pyo.value(constr_obj.upper) if constr_obj.upper is not None else 'inf',
                        'slack': slack,
                        'is_active': slack == 0 if slack is not None else False
                    }
                    if include_expr:
                        entry['expr'] = str(constr_obj.expr)
                    report.append(entry)
                except Exception as e:
                    entry = {
                        **base_info,
                        'constraint_type': 'Unknown',
                        'index': None,
                        'lower': f"Error: {str(e)}",
                        'body': f"Error: {str(e)}",
                        'upper': f"Error: {str(e)}",
                        'slack': None,
                        'is_active': False
                    }
                    if include_expr:
                        entry['expr'] = "N/A"
                    report.append(entry)
        df = pd.DataFrame(report)
        self.results['constraints'] = df
        return df
    def generate_objective_report(self, include_expr: bool = False, filter_func: Optional[Callable] = None) -> pd.DataFrame:
        """
        生成包含所有目标函数详细信息的结构化报告
        
        参数:
            include_expr: 是否包含原始表达式字符串
            filter_func: 筛选函数,接受组件对象,返回布尔值
            
        返回:
            pandas.DataFrame 包含目标函数信息的报表
        """
        report = []
        for obj_obj in self.model.component_objects(pyo.Objective, descend_into=True):
            # 应用筛选函数
            if filter_func and not filter_func(obj_obj):
                continue
                
            # 基础信息
            base_info = {
                'component': obj_obj.name,
                'doc': getattr(obj_obj, 'doc', None),
                'sense': 'minimize' if obj_obj.sense == pyo.minimize else 'maximize',
                'active': obj_obj.active
            }
            try:
                entry = {
                    **base_info,
                    'value': pyo.value(obj_obj)
                }
                if include_expr:
                    entry['expr'] = str(obj_obj.expr)
                report.append(entry)
            except Exception as e:
                entry = {
                    **base_info,
                    'value': f"Error: {str(e)}"
                }
                if include_expr:
                    entry['expr'] = "N/A"
                report.append(entry)
        df = pd.DataFrame(report)
        self.results['objectives'] = df
        return df
    
    def generate_sensitivity_report(self, solver=None):
        """
        生成约束灵敏度分析报告
        
        参数:
            solver: 已求解的求解器对象,如使用pyomo求解时的结果对象
            
        返回:
            pandas.DataFrame 包含灵敏度分析信息的报表
        """
        # 首先确保已生成约束报告
        if 'constraints' not in self.results:
            self.generate_constraint_report()
            
        constraints_df = self.results['constraints'].copy()
        
        # 检查是否有对偶值
        has_duals = False
        for constr_obj in self.model.component_objects(pyo.Constraint, descend_into=True):
            if hasattr(constr_obj, 'dual'):
                has_duals = True
                break
                
        if not has_duals:
            print("警告: 模型中未找到对偶值信息。请确保模型已求解并且启用了对偶值计算。")
            return pd.DataFrame()
            
        sensitivity_data = []
        
        for _, row in constraints_df.iterrows():
            constr_name = row['component']
            index = row['index']
            
            # 获取约束对象
            constr_obj = getattr(self.model, constr_name)
            
            # 处理索引约束
            if index is not None:
                try:
                    # 尝试将索引字符串转换回原始类型
                    idx = eval(index) if '(' in index else index
                    dual_value = pyo.value(constr_obj[idx].dual)
                except:
                    dual_value = None
            else:
                # 处理标量约束
                try:
                    dual_value = pyo.value(constr_obj.dual)
                except:
                    dual_value = None
                    
            # 记录灵敏度信息
            sensitivity_data.append({
                'constraint': constr_name,
                'index': index,
                'constraint_type': row['constraint_type'],
                'slack': row['slack'],
                'is_active': row['is_active'],
                'dual_value': dual_value,
                'shadow_price': dual_value if row['is_active'] else 0
            })
            
        sensitivity_df = pd.DataFrame(sensitivity_data)
        self.sensitivities['constraints'] = sensitivity_df
        return sensitivity_df
    
    def export_report(self, format: str = 'csv', directory: str = './reports', include_expr: bool = False, include_sensitivity: bool = False):
        """
        导出所有报表到文件
        
        参数:
            format: 导出格式,可选 'csv', 'excel', 'html', 'json'
            directory: 导出目录
            include_expr: 是否包含原始表达式
            include_sensitivity: 是否包含灵敏度分析
        """
        # 创建目录
        os.makedirs(directory, exist_ok=True)
        
        # 确保所有报表已生成
        self.generate_expression_report(include_expr=include_expr)
        self.generate_variable_report(include_expr=include_expr)
        self.generate_parameter_report(include_expr=include_expr)
        self.generate_constraint_report(include_expr=include_expr)
        self.generate_objective_report(include_expr=include_expr)
        
        # 如果需要,生成灵敏度分析
        if include_sensitivity:
            self.generate_sensitivity_report()
        
        # 导出函数映射
        export_funcs = {
            'csv': lambda df, path: df.to_csv(path, index=False),
            'excel': lambda df, path: df.to_excel(path, index=False),
            'html': lambda df, path: df.to_html(path, index=False),
            'json': lambda df, path: df.to_json(path, orient='records', indent=2)
        }
        
        # 检查格式是否支持
        if format not in export_funcs:
            raise ValueError(f"不支持的导出格式: {format}。支持的格式: {list(export_funcs.keys())}")
        
        # 导出各报表
        for name, df in self.results.items():
            if df is not None and not df.empty:
                path = os.path.join(directory, f"{self.name}_{name}.{format}")
                export_funcs[format](df, path)
                print(f"已导出 {name} 报表到: {path}")
        
        # 导出灵敏度分析
        if include_sensitivity and 'constraints' in self.sensitivities:
            path = os.path.join(directory, f"{self.name}_sensitivity_constraints.{format}")
            export_funcs[format](self.sensitivities['constraints'], path)
            print(f"已导出约束灵敏度分析到: {path}")
    
    def visualize_variables(self, output_path: Optional[str] = None, figsize: tuple = (12, 8)):
        """
        可视化变量值
        
        参数:
            output_path: 输出图像路径,若为None则显示图像
            figsize: 图像大小
        """
        # 确保变量报表已生成
        if 'variables' not in self.results:
            self.generate_variable_report()
            
        df = self.results['variables']
        
        # 过滤掉有错误的行
        df = df[~df['value'].astype(str).str.startswith('Error:')]
        
        if df.empty:
            print("没有有效的变量数据可可视化")
            return
            
        # 创建图表
        plt.figure(figsize=figsize)
        
        # 按组件分组
        for component, group in df.groupby('component'):
            # 检查是否为索引变量
            if group['index'].notna().any():
                # 索引变量,绘制散点图
                x = group['index'].astype(str)
                y = group['value'].astype(float)
                plt.scatter(x, y, label=component)
            else:
                # 标量变量,绘制条形图
                plt.bar(component, group['value'].astype(float), label=component)
                
        plt.title('模型变量值可视化')
        plt.xlabel('变量名称/索引')
        plt.ylabel('值')
        plt.legend()
        plt.grid(True, linestyle='--', alpha=0.7)
        plt.tight_layout()
        
        if output_path:
            plt.savefig(output_path)
            print(f"变量可视化已保存到: {output_path}")
        else:
            plt.show()
    
    def visualize_constraints(self, output_path: Optional[str] = None, figsize: tuple = (12, 8)):
        """
        可视化约束松弛值
        
        参数:
            output_path: 输出图像路径,若为None则显示图像
            figsize: 图像大小
        """
        # 确保约束报表已生成
        if 'constraints' not in self.results:
            self.generate_constraint_report()
            
        df = self.results['constraints']
        
        # 过滤掉有错误的行和没有松弛值的行
        df = df[~df['slack'].isna()]
        df = df[~df['body'].astype(str).str.startswith('Error:')]
        
        if df.empty:
            print("没有有效的约束数据可可视化")
            return
            
        # 创建图表
        plt.figure(figsize=figsize)
        
        # 按约束类型分组
        for constr_type, group in df.groupby('constraint_type'):
            # 检查是否为索引约束
            if group['index'].notna().any():
                # 索引约束,绘制散点图
                x = group['component'] + '_' + group['index'].astype(str)
                y = group['slack'].astype(float)
                plt.scatter(x, y, label=constr_type)
            else:
                # 标量约束,绘制条形图
                x = group['component']
                y = group['slack'].astype(float)
                plt.bar(x, y, label=constr_type)
                
        # 添加水平线表示零松弛(活动约束)
        plt.axhline(y=0, color='r', linestyle='-', alpha=0.5)
        
        plt.title('约束松弛值可视化')
        plt.xlabel('约束名称/索引')
        plt.ylabel('松弛值')
        plt.legend()
        plt.grid(True, linestyle='--', alpha=0.7)
        plt.xticks(rotation=45, ha='right')
        plt.tight_layout()
        
        if output_path:
            plt.savefig(output_path)
            print(f"约束可视化已保存到: {output_path}")
        else:
            plt.show()
    
    def visualize_objectives(self, output_path: Optional[str] = None, figsize: tuple = (10, 6)):
        """
        可视化目标函数值
        
        参数:
            output_path: 输出图像路径,若为None则显示图像
            figsize: 图像大小
        """
        # 确保目标函数报表已生成
        if 'objectives' not in self.results:
            self.generate_objective_report()
            
        df = self.results['objectives']
        
        # 过滤掉有错误的行
        df = df[~df['value'].astype(str).str.startswith('Error:')]
        
        if df.empty:
            print("没有有效的目标函数数据可可视化")
            return
            
        # 创建图表
        plt.figure(figsize=figsize)
        
        # 按目标类型分组
        for sense, group in df.groupby('sense'):
            x = group['component']
            y = group['value'].astype(float)
            plt.bar(x, y, label=f"{sense.capitalize()} ({len(group)} 个目标)")
            
        plt.title('目标函数值可视化')
        plt.xlabel('目标函数名称')
        plt.ylabel('值')
        plt.legend()
        plt.grid(True, linestyle='--', alpha=0.7)
        plt.tight_layout()
        
        if output_path:
            plt.savefig(output_path)
            print(f"目标函数可视化已保存到: {output_path}")
        else:
            plt.show()
    
    def generate_full_report(self, format: str = 'html', directory: str = './reports', include_expr: bool = False, include_sensitivity: bool = False, include_visualizations: bool = True):
        """
        生成完整的HTML格式报告,包含所有报表和可视化
        
        参数:
            format: 导出格式,可选 'html', 'md'
            directory: 导出目录
            include_expr: 是否包含原始表达式
            include_sensitivity: 是否包含灵敏度分析
            include_visualizations: 是否包含可视化
        """
        if format not in ['html', 'md']:
            raise ValueError("完整报告仅支持HTML和Markdown格式")
            
        # 确保所有报表已生成
        self.generate_expression_report(include_expr=include_expr)
        self.generate_variable_report(include_expr=include_expr)
        self.generate_parameter_report(include_expr=include_expr)
        self.generate_constraint_report(include_expr=include_expr)
        self.generate_objective_report(include_expr=include_expr)
        
        # 如果需要,生成灵敏度分析
        if include_sensitivity:
            self.generate_sensitivity_report()
            
        # 创建目录
        os.makedirs(directory, exist_ok=True)
        viz_dir = os.path.join(directory, 'visualizations')
        os.makedirs(viz_dir, exist_ok=True)
        
        # 生成可视化
        if include_visualizations:
            self.visualize_variables(os.path.join(viz_dir, 'variables.png'))
            self.visualize_constraints(os.path.join(viz_dir, 'constraints.png'))
            self.visualize_objectives(os.path.join(viz_dir, 'objectives.png'))
        
        # 生成报告
        if format == 'html':
            self._generate_html_report(directory, include_visualizations)
        else:
            self._generate_markdown_report(directory, include_visualizations)
    
    def _generate_html_report(self, directory: str, include_visualizations: bool):
        """生成HTML格式的完整报告"""
        report_path = os.path.join(directory, f"{self.name}_full_report.html")
        
        with open(report_path, 'w', encoding='utf-8') as f:
            # 写入HTML头部
            f.write("""<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Pyomo模型分析报告</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; line-height: 1.6; }
        h1, h2, h3 { color: #2c3e50; }
        table { border-collapse: collapse; width: 100%; margin-bottom: 20px; }
        th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
        th { background-color: #f2f2f2; }
        tr:nth-child(even) { background-color: #f9f9f9; }
        img { max-width: 100%; height: auto; margin-bottom: 20px; }
        .section { margin-bottom: 40px; }
        .summary { background-color: #e8f4ff; padding: 15px; border-radius: 5px; margin-bottom: 20px; }
    </style>
</head>
<body>
    <h1>Pyomo模型分析报告</h1>
    <div class="summary">
        <p><strong>模型名称:</strong> {}</p>
        <p><strong>生成时间:</strong> {}</p>
        <p><strong>组件统计:</strong> {}个变量, {}个参数, {}个约束, {}个目标函数</p>
    </div>
""".format(
                self.name,
                datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
                len(self.results.get('variables', pd.DataFrame())),
                len(self.results.get('parameters', pd.DataFrame())),
                len(self.results.get('constraints', pd.DataFrame())),
                len(self.results.get('objectives', pd.DataFrame()))
            ))
            
            # 添加变量部分
            f.write("<div class='section'><h2>变量分析</h2>\n")
            if include_visualizations and os.path.exists(os.path.join(directory, 'visualizations', 'variables.png')):
                f.write("<img src='visualizations/variables.png' alt='变量值可视化'>\n")
            if 'variables' in self.results and not self.results['variables'].empty:
                f.write(self.results['variables'].to_html(index=False))
            else:
                f.write("<p>没有找到变量数据</p>")
            f.write("</div>\n")
            
            # 添加参数部分
            f.write("<div class='section'><h2>参数分析</h2>\n")
            if 'parameters' in self.results and not self.results['parameters'].empty:
                f.write(self.results['parameters'].to_html(index=False))
            else:
                f.write("<p>没有找到参数数据</p>")
            f.write("</div>\n")
            
            # 添加约束部分
            f.write("<div class='section'><h2>约束分析</h2>\n")
            if include_visualizations and os.path.exists(os.path.join(directory, 'visualizations', 'constraints.png')):
                f.write("<img src='visualizations/constraints.png' alt='约束松弛值可视化'>\n")
            if 'constraints' in self.results and not self.results['constraints'].empty:
                f.write(self.results['constraints'].to_html(index=False))
            else:
                f.write("<p>没有找到约束数据</p>")
            f.write("</div>\n")
            
            # 添加目标函数部分
            f.write("<div class='section'><h2>目标函数分析</h2>\n")
            if include_visualizations and os.path.exists(os.path.join(directory, 'visualizations', 'objectives.png')):
                f.write("<img src='visualizations/objectives.png' alt='目标函数值可视化'>\n")
            if 'objectives' in self.results and not self.results['objectives'].empty:
                f.write(self.results['objectives'].to_html(index=False))
            else:
                f.write("<p>没有找到目标函数数据</p>")
            f.write("</div>\n")
            
            # 添加灵敏度分析部分
            if 'constraints' in self.sensitivities and not self.sensitivities['constraints'].empty:
                f.write("<div class='section'><h2>约束灵敏度分析</h2>\n")
                f.write(self.sensitivities['constraints'].to_html(index=False))
                f.write("</div>\n")
            
            # 写入HTML尾部
            f.write("</body></html>")
            
        print(f"完整HTML报告已生成: {report_path}")
    
    def _generate_markdown_report(self, directory: str, include_visualizations: bool):
        """生成Markdown格式的完整报告"""
        report_path = os.path.join(directory, f"{self.name}_full_report.md")
        
        with open(report_path, 'w', encoding='utf-8') as f:
            # 写入Markdown头部
            f.write(f"""# Pyomo模型分析报告
**模型名称:** {self.name}
**生成时间:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
**组件统计:** {len(self.results.get('variables', pd.DataFrame()))}个变量, {len(self.results.get('parameters', pd.DataFrame()))}个参数, {len(self.results.get('constraints', pd.DataFrame()))}个约束, {len(self.results.get('objectives', pd.DataFrame()))}个目标函数
""")
            
            # 添加变量部分
            f.write("## 变量分析\n\n")
            if include_visualizations and os.path.exists(os.path.join(directory, 'visualizations', 'variables.png')):
                f.write("\n\n")
            if 'variables' in self.results and not self.results['variables'].empty:
                f.write(self.results['variables'].to_markdown(index=False))
            else:
                f.write("没有找到变量数据\n\n")
            
            # 添加参数部分
            f.write("\n## 参数分析\n\n")
            if 'parameters' in self.results and not self.results['parameters'].empty:
                f.write(self.results['parameters'].to_markdown(index=False))
            else:
                f.write("没有找到参数数据\n\n")
            
            # 添加约束部分
            f.write("\n## 约束分析\n\n")
            if include_visualizations and os.path.exists(os.path.join(directory, 'visualizations', 'constraints.png')):
                f.write("\n\n")
            if 'constraints' in self.results and not self.results['constraints'].empty:
                f.write(self.results['constraints'].to_markdown(index=False))
            else:
                f.write("没有找到约束数据\n\n")
            
            # 添加目标函数部分
            f.write("\n## 目标函数分析\n\n")
            if include_visualizations and os.path.exists(os.path.join(directory, 'visualizations', 'objectives.png')):
                f.write("\n\n")
            if 'objectives' in self.results and not self.results['objectives'].empty:
                f.write(self.results['objectives'].to_markdown(index=False))
            else:
                f.write("没有找到目标函数数据\n\n")
            
            # 添加灵敏度分析部分
            if 'constraints' in self.sensitivities and not self.sensitivities['constraints'].empty:
                f.write("\n## 约束灵敏度分析\n\n")
                f.write(self.sensitivities['constraints'].to_markdown(index=False))
            
        print(f"完整Markdown报告已生成: {report_path}")
# 使用示例
if __name__ == "__main__":
    # 创建测试模型
    from pyomo.environ import ConcreteModel, Var, Param, Expression, Constraint, Objective, minimize, maximize
    m = ConcreteModel()
    # 添加标量变量和索引变量
    m.x = Var(initialize=1.5, doc="标量测试变量")
    m.index_set = [1, 2, 3]
    m.y = Var(m.index_set, initialize={1: 10, 2: 20, 3: 30}, doc="索引测试变量")
    # 添加标量参数和索引参数
    m.a = Param(initialize=5, doc="标量测试参数")
    m.b = Param(m.index_set, initialize={1: 100, 2: 200, 3: 300}, doc="索引测试参数")
    # 添加标量表达式
    m.scalar_expr = Expression(expr=1.5, doc="标量测试表达式")
    # 添加索引表达式
    m.index_set = [1, 2, 3]
    m.param = Param(m.index_set, initialize={1: 10, 2: 20, 3: 30}, doc="索引测试参数")
    m.indexed_expr = Expression(m.index_set,
                              rule=lambda m, i: m.param[i] * 2,
                              doc="索引测试表达式")
    
    # 添加标量不等式约束
    m.scalar_ineq_constr = Constraint(expr=m.x >= 1, doc="标量不等式约束")
    # 添加标量等式约束
    m.scalar_eq_constr = Constraint(expr=m.x == 2, doc="标量等式约束")
    # 添加索引约束
    m.index_set = [1, 2, 3]
    m.indexed_ineq_constr = Constraint(m.index_set,
                                       rule=lambda m, i: m.x >= i * 0.5,
                                       doc="索引不等式约束")
    m.indexed_eq_constr = Constraint(m.index_set,
                                     rule=lambda m, i: m.x == i * 0.5 + 0.5,
                                     doc="索引等式约束")
    # 添加目标函数
    m.obj1 = Objective(expr=m.x ** 2, sense=minimize, doc="最小化目标函数")
    m.obj2 = Objective(expr=-m.x ** 2, sense=maximize, doc="最大化目标函数")
    # 创建报表生成器
    reporter = PyomoModelReporter(m, name="示例模型分析")
    
    # 生成变量报表并显示
    var_report = reporter.generate_variable_report()
    print("变量报表预览:")
    print(var_report.head())
    
    # 生成约束报表并显示
    constr_report = reporter.generate_constraint_report()
    print("\n约束报表预览:")
    print(constr_report.head())
    
    # 可视化变量
    reporter.visualize_variables()
    
    # 可视化约束
    reporter.visualize_constraints()
    
    # 生成完整HTML报告
    reporter.generate_full_report(format='html', include_expr=False, include_sensitivity=False)
    
    # 导出所有报表为CSV
    reporter.export_report(format='csv', include_expr=True)
                    
                
                
            
        
浙公网安备 33010602011771号