coin-or / python-mip

Python-MIP: collection of Python tools for the modeling and solution of Mixed-Integer Linear programs
Eclipse Public License 2.0
513 stars 88 forks source link

Improve Example Code Readability #340

Open mwerezak opened 1 year ago

mwerezak commented 1 year ago

Maybe this is because I'm coming from more of a software engineering background than an operations research background, but I find the code examples in the documentation to be very dense and hard to read.

I like the examples, there are some interesting problems there and the code samples do show you how to use the mip package to get solutions to them.

However, if you put yourself in the shoes of someone who is evaluating mip and trying to imagine how it could be integrated into an actual production system, the code examples fall pretty short.

I figure this is a pretty low-priority issue, but I thought I should at least bring up the topic and provide an example of how the code samples could be improved.

I've re-written the Plant Location with Non-Linear Costs example as a way of showing what could be possible. If you're interested, please compare my re-written version with the original one. I believe my version is much more readable and would give a reader who is unfamiliar with mip a better sense of how it could be integrated into a larger system.

mwerezak commented 1 year ago

Another example, Resource Constrained Project Scheduling.

I have to say, I think it is a huge improvement in showing how the solution works. The expressions used to setup the constraints read far more clearly than the code sample on the examples page.

Just compare:

# Constraint: resource usage
for res in RESOURCES:
    for t in range(PLANNING_HORIZON):
        total_usage = mip.xsum(
            job.data.resource_usage.get(res, 0) * job.starts_at[s]
            for job in JOBS.values()
            for s in job.possible_start_times(t) if s >= 0
        )
        mip_model.add_constr( total_usage <= res.capacity )

# Constraint: job dependencies
successors: dict[Job, list[Job]] = {
    job : [ suc for suc in JOBS.values() if job.data in suc.data.dependencies ]
    for job in JOBS.values()
}
for job, suc_list in successors.items():
    for suc in suc_list:
        mip_model.add_constr( job.completed_time <= suc.start_time )

# Objective: minimize the time at which all jobs are complete
all_done = Event(mip_model, 'all_done')
for final_job in (JOBS[8], JOBS[9], JOBS[10]):
    mip_model.add_constr( final_job.completed_time <= all_done.time )

mip_model.objective = mip.minimize( all_done.time )

with

model.objective = xsum(t * x[n + 1][t] for t in T)

for j in J:
    model += xsum(x[j][t] for t in T) == 1

for (r, t) in product(R, T):
    model += (
        xsum(u[j][r] * x[j][t2] for j in J for t2 in range(max(0, t - p[j] + 1), t + 1))
        <= c[r])

for (j, s) in S:
    model += xsum(t * x[s][t] - t * x[j][t] for t in T) >= p[j]
christian2022 commented 1 year ago

I like these modifications to the plant location example, it makes the examples more pythonic. Recommended changes (would have prefered to comment inline, but don't know how):

sebheger commented 1 year ago

@mwerezak Feel free to add a PR. I would add this as a separate examples