3
\$\begingroup\$

Some background before the actual code:

My team needs to develop an optimization library for a specific problem. In order to optimize (pun intended) the projects life-cycle I am trying to write some interfaces that my peers (who are data scientist with small interest and experience in programming) can easily implement. For this reason the only requirement I am looking to fulfill is the readability and extensibility of the code, as it will never go into production.

The optimizer interface:

class ProtonOptimizer(object):
"""
An abstract class defining a common interface to be used by all our future models.
Make sure you comply to the API meaning that your concrete class should:
1) Be constructed by providing the BED values as a 2D np array and the capacity
2) It should return its result by implementing the get_optimum() method
"""
  def __init__(self, BED, capacity = 100, model_name = 'abstract_therapy'):
    raise NotImplementedError("You cannot construct an abstract class")

  def get_optimum():
    """Returns a dictionary (int -> int) from patient ID to fractions"""
    return {}

  def pretty_print():
    """Override to print your models output in a meaningful way for debugging purposes"""
    print("The optimum is " + str(self.get_optimum()))

The sample implementation I provided:

class LPOptimizer(ProtonOptimizer):
"""Concrete implementation of the ProtonOptimizer interface"""
  def __init__(self, BED, capacity = 100, model_name = 'proton_therapy'):
    self._BED = BED
    num_patients, max_fractions_per_patient = BED.shape
    self.patients = [i for i in range(num_patients)]
    self.fractions = [j for j in range(max_fractions_per_patient)]
    self.m = Model(model_name)

    # Set binary decision variables
    self.x = self.m.addVars(num_patients, max_fractions_per_patient, vtype = GRB.BINARY) 

    # We can only perform so many proton therapies per week
    self.m.addConstr(quicksum(
        quicksum(self.x[i,j] * self.fractions[j] for j in self.fractions) 
        for i in self.patients) <= capacity)
    self.m.update()

    self.optimum = {}


  def _solve(self, debug = False):
    # Set objective
    self.m.setObjective(quicksum(
        self.x[i,j] * self._BED[i,j] for i in self.patients for j in self.fractions),
                        GRB.MAXIMIZE)

    self.m.setParam('OutputFlag', debug)
    self.m.update()
    self.m.optimize()
    if self.m.status == GRB.Status.OPTIMAL:
        solution = self.m.getAttr("x", self.x)
        for i in self.patients:
            for j in self.fractions:
                if(solution[i,j] == 1):
                    self.optimum[i] = j
                    break
    else:
        print("Infeasible model")



  def pretty_print(self):
    solution = self.get_optimum()
    for patient, fractions in solution.items():
        print(("Patient " + str(patient) + " should receive " + str(fractions) + " fractions"))

  def get_optimum(self):
    if not self.optimum:
        self._solve()
    return self.optimum

The actual question: The goal here is to enable my peers to write other implementations of the ProtonOptimizer but be able to test all of them uniformly later (this is why i demand that every implementation has a get_optimum() function that returns a dict). Is this design actually helping them achieve it? How can I make it as easy for them as possible to contribute?

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.