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

如何借助IBM CPLEX Python API实现符合团队容量与前置依赖约束的迭代需求筛选方案

Solving Story Selection with IBM CPLEX Python API: Fixing Optional Variables & Capacity Constraints

Hey there, let's break down your problem and fix those core pain points you're facing with CPLEX. The two main issues—optional variables not selecting any stories, and mandatory variables causing infeasibility—stem from missing key model components: a clear objective function, incorrect capacity constraint logic, and improper handling of optional variables in constraints.

1. Why Optional Variables Aren't Selecting Stories

Right now, your model has no objective function to guide its decisions. CPLEX needs a goal to optimize—like maximizing the number of stories selected, or prioritizing higher story point values—otherwise, it has no incentive to pick any optional variables. The default behavior in this case is to select nothing, which trivially satisfies all constraints.

2. Fixing the Capacity Constraint Logic

Your current capacity constraint approach is flawed. Instead of manually subtracting step functions, use CPLEX's cumul_function to track resource usage properly, and tie it to whether an optional story is selected. Since you need the total selected story points to exactly match the team's sprint capacity, we'll structure constraints to enforce this per iteration.

3. Properly Handling Optional Variables & Dependencies

For optional interval variables, use presence_of() to check if a story is selected. When adding predecessor-successor dependencies, only enforce them if both the parent and child stories are included in the final selection.


Step-by-Step Implementation Fixes

Let's rewrite your code with these corrections:

First, Add an Objective Function

Choose an objective that aligns with your priorities. For example, maximize the number of selected stories:

# Objective: Maximize the count of selected stories
mdl0.maximize(sum(presence_of(var) for var in dictIntrvalVars.values()))

Or if you want to prioritize higher story points (while still hitting capacity targets):

# Objective: Maximize total story points of selected stories
total_points = sum(
    presence_of(var) * int(dfAnalyzerOrg.loc[dfAnalyzerOrg['Stories'] == name, 'Story Points'].iloc[0]) 
    for name, var in dictIntrvalVars.items()
)
mdl0.maximize(total_points)

Correct the Capacity Constraint

Use cumul_function to track team capacity usage, and constrain total usage to exactly match each sprint's capacity:

NB_SPRINTS = 2
SPRINT_INTERVAL = 20  # Ensure this matches your time unit consistency (e.g., days)

for team, lst_team in dictTeamWise.items():
    # Get the team's capacity per sprint
    sprint_capacities = [
        dfTeamCapacity.loc[dfTeamCapacity['Team'] == team, s+1].iloc[0] 
        for s in range(NB_SPRINTS)
    ]
    
    # Create a cumulative function to track the team's capacity consumption
    team_usage = mdl0.cumul_function()
    
    # Add usage for each story only if it's selected
    for story_name, interval_var in lst_team:
        story_points = int(dfAnalyzerOrg.loc[dfAnalyzerOrg['Stories'] == story_name, 'Story Points'].iloc[0])
        # Tie story point usage to the presence of the interval variable
        team_usage += step_at_start(interval_var, story_points)
    
    # Enforce that total usage in each sprint exactly matches the capacity
    for sprint_idx in range(NB_SPRINTS):
        sprint_start = sprint_idx * SPRINT_INTERVAL
        sprint_end = (sprint_idx + 1) * SPRINT_INTERVAL
        mdl0.add(
            team_usage.at(sprint_end) - team_usage.at(sprint_start) == sprint_capacities[sprint_idx]
        )

Add Predecessor-Successor Dependencies

If you have a list of dependencies (e.g., a dataframe with Predecessor and Successor columns), enforce constraints only when both stories are selected:

# Example: Assume dfDependencies has columns 'Predecessor' and 'Successor'
for idx, dep_row in dfDependencies.iterrows():
    pred_story = dep_row['Predecessor']
    succ_story = dep_row['Successor']
    
    if pred_story in dictIntrvalVars and succ_story in dictIntrvalVars:
        pred_var = dictIntrvalVars[pred_story]
        succ_var = dictIntrvalVars[succ_story]
        
        # Only enforce end_before_start if both stories are selected
        mdl0.add(
            mdl0.implies(
                presence_of(pred_var) & presence_of(succ_var),
                end_before_start(pred_var, succ_var)
            )
        )

Fix Optional Variable Initialization

Ensure your optional variable logic is intentional. If you want all stories to be eligible for selection, set optional=True for all:

dictIntrvalVars = {}
for idx, row in dfAnalyzerOrg.iterrows():
    # Set all stories as optional (adjust if some are mandatory)
    is_optional = True  # Replace with your original logic if needed
    dictIntrvalVars[row['Stories']] = mdl0.interval_var(
        name=row['Stories'],
        size=int(row['Story Points']),
        optional=is_optional
    )

Additional Troubleshooting Tips

  • Infeasibility Checks: If the model remains infeasible, use CPLEX's conflict detection to identify problematic constraints:
    if not mdl0.solve():
        print("Model is infeasible. Running conflict detection...")
        mdl0.conflict_refine()
        print(mdl0.conflict())
    
  • Relax Strict Capacity Rules: If "exact match" is too rigid, allow a small tolerance (e.g., >= capacity * 0.95 and <= capacity) if your business rules permit it.
  • Time Unit Consistency: Ensure SPRINT_INTERVAL and interval variable size use the same time units (e.g., story points mapped to hours, or days).

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.27 16:52:49