HLPL / VerTeX Modelling Language Documentation

← examples

# Resource Constrained Scheduling Problem (RCSP)
# Schedules activities to maximize net present value of cash flows
# Subject to resource capacity constraints and activity durations
# Demonstrates Excel data loading and parameterized set functions

model rcsp
set I = load_activities()                                   # Activities to schedule
set T = load_time_periods()                                 # Time periods
set R = load_resources()                                    # Resources

set delta(I, T) = load_delta()                             # Time periods when activity i is active if started at t

const d = load_durations(), forall i in I                  # Duration of activity i
const u = load_resource_limits(), forall r in R            # Upper limit of resource r
const v = load_resource_values(), forall i in I, forall r in R  # Resource r consumed by activity i
const dc = load_discounted_cf(), forall i in I, forall t in T   # Discounted cash flow if activity i starts at t

bin x, forall i in I, forall t in T                        # 1 if activity i starts at time t

# Objective: Maximize net present value of cash flows
max sum_{t in T}{sum_{i in I}{dc_{i,t} * x_{i,t}}}

# Each activity must start exactly once
constr sum_{t in T}{x_{i,t}} = 1, forall i in I

# Resource capacity constraint at each time period
constr sum_{i in I}{sum_{k in delta(i, t)}{v_{i,r} * x_{i,k}}} <= u_{r}, forall t in T, forall r in R
end

import elytica
import pandas as pd
import json

# Excel file with three sheets: activities, resources, consumption
xls = "scheduling.xlsx"

# Load data from Excel
activity_sheet = json.loads((pd.read_excel(xls, sheet_name="activities")).to_json(orient='records'))
resource_sheet = json.loads((pd.read_excel(xls, sheet_name="resources")).to_json(orient='records'))
consumption_sheet = json.loads((pd.read_excel(xls, sheet_name="consumption")).to_json(orient='records'))

# Process activity data
activities = [i["activity_id"] for i in activity_sheet]
durations = {i["activity_id"]: i["duration"] for i in activity_sheet}
cash_flows = {i["activity_id"]: i["cash_flow"] for i in activity_sheet}

# Generate time periods based on total duration
time_periods = list(range(1, sum(durations.values())))

# Process resource data
resources = [r["resource_id"] for r in resource_sheet]
resource_limits = {r["resource_id"]: r["upper_limit"] for r in resource_sheet}

# Process consumption data
resource_values = {
    i["activity_id"]: {
        r["resource_id"]: r["value"]
        for r in consumption_sheet
        if r["activity_id"] == i["activity_id"]
    }
    for i in consumption_sheet
}

def load_activities():
    """Return list of activity IDs"""
    return activities

def load_time_periods():
    """Return list of time periods"""
    return time_periods

def load_resources():
    """Return list of resource IDs"""
    return resources

def load_durations():
    """Return duration for each activity"""
    return durations

def load_resource_limits():
    """Return capacity limit for each resource"""
    return resource_limits

def load_resource_values():
    """Return resource consumption for each activity and resource"""
    return resource_values

def load_discounted_cf():
    """Calculate discounted cash flow for each activity at each start time (10% discount rate)"""
    return {
        i: {t: cash_flows[i] / (1.1)**t for t in time_periods}
        for i in activities
    }

def load_delta():
    """
    Return time periods when activity i is active if started at time t
    delta(i,t) = [t, t+1, ..., t+d[i]-1]
    """
    return {
        i: {
            t: [k for k in time_periods if k >= t and k <= t + durations[i] - 1]
            for t in time_periods
        }
        for i in activities
    }

def main():
    elytica.init_model("rcsp")
    elytica.run_model("rcsp")

    I = elytica.get_model_set("rcsp", "I")
    T = elytica.get_model_set("rcsp", "T")

    print("\n" + "="*70)
    print("RESOURCE CONSTRAINED SCHEDULING SOLUTION")
    print("="*70)

    # Collect schedule
    schedule = []
    print("\nActivity Schedule:")
    print("-"*70)

    for i in I:
        for t in T:
            vname = f"x{i},{t}"
            val = elytica.get_variable_value("rcsp", vname)
            if val > 0.5:
                duration = durations[i]
                end_time = t + duration - 1
                schedule.append({
                    "activity": i,
                    "start_time": t,
                    "end_time": end_time,
                    "duration": duration
                })
                print(f"  Activity {i:3d}: Start={t:3d}, End={end_time:3d}, Duration={duration:2d}")

    # Sort schedule by start time
    schedule.sort(key=lambda x: x["start_time"])

    # Get objective value (NPV)
    obj_value = elytica.get_best_primal_bound("rcsp")

    print("-"*70)
    print(f"Net Present Value (NPV): {obj_value:.2f}")
    print("="*70)

    # Prepare results for output
    results = {
        "objective_value": obj_value,
        "schedule": schedule
    }

    elytica.write_results(json.dumps(results, indent=2))

    return 0


# Expected Excel file format (scheduling.xlsx):
#
# Sheet "activities":
# | activity_id | duration | cash_flow |
# |-------------|----------|-----------|
# | 1           | 3        | 1000      |
# | 2           | 2        | 500       |
# | ...         | ...      | ...       |
#
# Sheet "resources":
# | resource_id | upper_limit |
# |-------------|-------------|
# | 1           | 10          |
# | 2           | 8           |
# | ...         | ...         |
#
# Sheet "consumption":
# | activity_id | resource_id | value |
# |-------------|-------------|-------|
# | 1           | 1           | 5     |
# | 1           | 2           | 3     |
# | 2           | 1           | 2     |
# | ...         | ...         | ...   |

Read this through MCP: read with path: "examples/rcsp", or resource hlpl://examples/rcsp.