Decision Optimization

Decision Optimization

Delivers prescriptive analytics capabilities and decision intelligence to improve decision-making.

 View Only
Expand all | Collapse all

Modifying Sched_tasks example

  • 1.  Modifying Sched_tasks example

    Posted Tue June 25, 2024 02:26 PM

    Hello,
    I am modifying the Sched_tasks example to make it applicable to optimize several tasks across scattered units with different durations.

    Here is my .mod file:

    using CP;
    int NbHouses = ...; // Number of units
    range HousesRange = 1..NbHouses;
     
    int NoTasks = ...; // Number of tasks
    range TasksRange = 1..NoTasks;
     
    {string} WorkerNames = ...;  // List of workers' names
    string TaskNames[TasksRange] = ...; // List of task names as an array of strings
    {string} ActivityNames = {}; // List of task names as a set of strings
     
    // Use the data from TaskNames to create the ActivityNames
    execute {
      for (var i in TasksRange) {
        ActivityNames.add(TaskNames[i]);
      }
    }
     
    // Create a tuple containing id and name of tasks
    tuple ListOfTask {
      int id;
      string TaskName;
    };
    {ListOfTask} tasksAsTuples = {<i, TaskNames[i]> | i in TasksRange};
     
    execute {
      writeln("tasksAsTuples: ", tasksAsTuples);
    }
     
    int Duration[HousesRange][TasksRange] = ...; // Duration for each task in each house as a matrix
     
    // Data for workers
    tuple ResourceDat {
      key int id;
      string type;
      string name;
    };
     
    {ResourceDat} resources = ...;
     
    // Create a list of the resource types
    {string} resourceTypes = {r.type | r in resources};
    // Data for task & worker requirements (Which crew to complete a task and how many required)
    tuple Requirement {
      string task;
      string resource;
      int quantity;
    };
     
    {Requirement} requirements = ...;
    // Create a tuple with data of list of tasks & requirements for each one & available crews
    tuple Allocation {
      ListOfTask dmd;
      Requirement req;
      ResourceDat resource;
    };
     
    {Allocation} allocs = 
      {<tn, m, r> | tn in tasksAsTuples, m in requirements, r in resources : 
       tn.TaskName == m.task && r.type == m.resource};
    // Relationships among tasks
    tuple Precedence {
      string pre;
      string post;
      string type;
      int delay;
    };
     
    // Import the relationships from the .dat file
    {Precedence} Precedences = ...;
    int ReleaseDate[HousesRange] = ...; 
    int DueDate[HousesRange] = ...; 
    float Weight[HousesRange] = ...; 
     
    // Define the matrix to import the values
    int transitionTimes[HousesRange][HousesRange] = ...;
     
    // Transition time across the units
    tuple triplet {
      int loc1;
      int loc2;
      int value;
    };
     
    // Create a set of tuples from the traveling matrix
    {triplet} transitionAsTuples = {<i, j, transitionTimes[i][j]> | i in HousesRange, j in HousesRange};
     
    // Create the house interval variables
    dvar interval houses[h in HousesRange] in ReleaseDate[h]..(maxint div 2)-1;
     
    // Create the task interval variables
    dvar interval itvs[h in HousesRange][t in TasksRange] size Duration[h][t];
     
    // Create the allocation interval variables
    dvar interval tiallocs[allocs] optional;
     
    // Create the sequence variables
    dvar sequence workers[r in resources] in all(a in allocs: a.resource == r) tiallocs[a];
     
    int levels[rt in resourceTypes] = sum(r in resources : r.type == rt) 1;
     
    cumulFunction cumuls[rt in resourceTypes] =
      sum(rc in requirements, tn in tasksAsTuples, h in HousesRange, t in TasksRange : rc.resource == rt && tn.TaskName == rc.task) pulse(itvs[h][t], rc.quantity);
    // Declare the mapping from task names to task indices
    int taskIndex[ActivityNames];
     
    // Populate the mapping
    execute {
      for (var i in TasksRange) {
        taskIndex[TaskNames[i]] = i;
      }
    }
     
    // Define the objective Functions:
    // The total duration
    dexpr float TotalDuration = max(h in HousesRange, t in TasksRange) endOf(itvs[h][t]);
     
    // Determine the earliest start time among all houses
    dexpr float firstStartTime = min(h in HousesRange) startOf(houses[h]);
    // Objective Function:
    minimize TotalDuration;
     
    subject to {
     
    // Ensure the first task in the first house starts at day 0
    firstStartTime == 0;
     
    // Add the precedence constraints
    forall(h in HousesRange)
      forall(p in Precedences) 
        if (p.type == "FS") endBeforeStart(itvs[h][taskIndex[p.pre]], itvs[h][taskIndex[p.post]], p.delay);
        else if (p.type == "SS") startAtStart(itvs[h][taskIndex[p.pre]], itvs[h][taskIndex[p.post]], p.delay);
        else if (p.type == "FF") endAtEnd(itvs[h][taskIndex[p.pre]], itvs[h][taskIndex[p.post]], p.delay);
        else if (p.type == "SF") startBeforeEnd(itvs[h][taskIndex[p.pre]], itvs[h][taskIndex[p.post]], p.delay);
     
    // Add the house span constraints
    forall(h in HousesRange)
    span(houses[h], all(t in TasksRange) itvs[h][t]);
     
    // Add the no overlap constraints
    forall(r in resources) {
      noOverlap(workers[r], transitionAsTuples);
    }
     
    // Ensure resource levels are not exceeded
    forall(r in resourceTypes : levels[r] > 10) {
      cumuls[r] <= levels[r];
    }
    }
    // Set solver parameters
    execute {
      cp.param.FailLimit = 10000;
      cp.param.TimeLimit = 300;
      cp.param.TimeMode = "CPUTime";
      cp.param.SearchType = "auto";
      writeln("Solver parameters set.");
    }


    Here is my .dat file

    NbHouses = 3;
     
    NoTasks = 10;
     
    WorkerNames = {"Crew1-1", "Crew1-2","Crew2", "Crew3-1", "Crew3-2","Crew3-3","Crew4", "Crew5", "Crew6", "Crew7", "Crew8", "Crew9", "Crew10" };
     
    resources = {
      <1, MasonaryLayer,     "Crew1-1" >,
      <2, MasonaryLayer,     "Crew1-2" >,
      <3, Carpenter,         "Crew2" >,
      <4, Plumber,           "Crew3-1" >,
      <5, Plumber,           "Crew3-2" >,
      <6, Plumber,           "Crew3-3" >, 
      <7, CeilingWorker,     "Crew4" >,
      <8, RoofWorker,        "Crew5" >,
      <9, Painter,           "Crew6" >,
      <10, WindowInstaller,  "Crew7" >,
      <11, FacadeWorker,     "Crew8" >,
      <12, GardenWorker,     "Crew9" >,
      <13, Mover,            "Crew10" >
    };
     
    TaskNames = [ 
      "masonry", 
      "carpentry", 
      "plumbing", 
      "ceiling",
      "roofing", 
      "painting", 
      "windows", 
      "facade",
      "garden", 
      "moving" 
    ];
     
    // Each row represents a building (unit) with duration of tasks in the same order as in TaskNames list
     
    Duration =  [[35, 30, 25, 20, 25, 15, 20, 15, 20, 15],
        [10, 15, 10, 15, 10, 5, 10, 5, 10, 5],
        [5, 10, 5, 10, 5, 15, 20, 15, 20, 15]];
     
    requirements = {
      <masonry,      MasonaryLayer,    1>,
      <carpentry,    Carpenter,        1>,
      <plumbing,     Plumber,          1>,
      <ceiling,      CeilingWorker,    1>, 
      <roofing,      RoofWorker,       1>, 
      <painting,     Painter,          1>,
      <windows,      WindowInstaller,  1>, 
      <facade,       FacadeWorker,     1>,
      <garden,       GardenWorker,     1>,
      <moving,       Mover,            1>
    };
     
    // transitionTimes to be entered as a fraction of day (e.g., 2 hrs = 0.25 day)
     
    transitionTimes = [[0, 2, 1],
                                 [2, 0, 1],
                                 [1, 1, 0]
    ];
     
    ReleaseDate = [  0,     0,   0];
    DueDate     = [120,   212,   304];
    Weight      = [100.0, 100.0, 100.0];
     
    // Format <"Predecessor", "Successor", "Relation type", Lag time>,
    // FS = Finish to Start; SS = Start to Start; FF = Finish to Finish; SF = Start to Finish
    // Lag = + Lag time ; Lead = - Lag time
     
    Precedences = {
      <"masonry",   "carpentry", "SS", 2>,
      <"masonry",   "plumbing", "FS", -2>,
      <"masonry",   "ceiling", "FS", 0>,
      <"carpentry", "roofing", "FS", 0>,
      <"ceiling",   "painting", "FS", 0>,
      <"roofing",   "windows", "FF", 0>,
      <"roofing",   "facade", "FS", 0>,
      <"plumbing",  "facade", "FS", 0>,
      <"roofing",   "garden", "FS", 0>,
      <"plumbing",  "garden", "SF", 0>,
      <"windows",   "moving", "FS", 0>,
      <"facade",    "moving", "FS", 0>,
      <"garden",    "moving", "FS", 0>,
      <"painting",  "moving", "FS", 0>
    };
    I have issues with lines highlighted in Bold (in the .mod file):
    1- Create the sequence variable for the workers
    2- cumulFunction for the resources consumption.
    Also, I need to calculate the idle time for each worker and the total idle time

    Any help would be much appreciated,
    Regards,


    ------------------------------
    Fam Saeed
    ------------------------------


  • 2.  RE: Modifying Sched_tasks example

    Posted Thu July 04, 2024 11:02 AM

    Hello Fam,

                 In your example "tasksAsTuples" corresponds to "operations" in the Sched_tasks example.

    You can update your definition as follows:

    {ListOfTask} tasksAsTuples = {<(h-1)*NoTasks + i, TaskNames[i]> | i in TasksRange, h in HousesRange};

    You want to build NbHouses houses.

    Regarding the sequence variables, if you want to take into account the transition times, you have to define a type for each interval vars.

    dvar sequence workers[r in resources] in all(a in allocs: a.resource == r) tiallocs[a] types all(a in allocs: a.resource == r) (((a.dmd.id-1) div NoTasks)+1) ;

    Regarding the cumul functions, "tasksAsTuples" contains now all the operations for all the houses, you can update your code as follows:

    cumulFunction cumuls[rt in resourceTypes] =

      sum(rc in requirements, tn in tasksAsTuples : rc.resource == rt && tn.TaskName == rc.task) pulse(itvs[((tn.id-1) div NoTasks) + 1][((tn.id mod NoTasks) == 0 ? NoTasks:(tn.id mod NoTasks))], rc.quantity);

    In the Sched_tasks example, the tiops interval variables are linked to the tiallocs interval variables using alternatives.

    You need to do the same thing between itvs and tiallocs interval variables:

    forall(h in HousesRange) {

      forall(t in TasksRange) {

        forall (rc in requirements : rc.task == TaskNames[t]) {

                 alternative(itvs[h][t], all(a in allocs : a.dmd.id == (h-1)*NoTasks+t) tiallocs[a], rc.quantity);

       }        

      }

    } 

    Regarding the redundant cumul function, you can keep the same as in Sched_tasks example:

      forall(r in resourceTypes: levels[r] > 1) {

        cumuls[r] <= levels[r];

      } 

    In your example you set some parameters. For them to be taken into account, you must define them before defining your objective function.

    Best regards,



    ------------------------------
    Thierry Sola
    ------------------------------



  • 3.  RE: Modifying Sched_tasks example

    Posted Wed September 25, 2024 09:34 PM

    Hi Thierry Sola,

    Thanks so much for your great help!
    I have one more part that I need your help with.

    I need to add a constraint that each worker (resource) can work across the units only in a sequential order. As an example, worker1 > units 1,2,3,4, etc or worker 2 > units 7,6,5,4, etc.

    I would appreciate any help.
    Regards,



    ------------------------------
    Fam Saeed
    ------------------------------



  • 4.  RE: Modifying Sched_tasks example

    Posted Thu September 26, 2024 08:32 AM

    Hello Fam Saeed,

                  In your model, a sequence var is defined for each worker. You can use the constraint before on the sequence variables to model your additional constraint:

    "before(p, a, b) states that if both intervals a and b are present, a will appear before b in the sequence p."

    The code might look like this:

      forall(a1 in allocs, a2 in allocs: a1.resource == a2.resource)

                   if (a1.resource == worker1 && a2.dmd.id < a1.dmd.id )

                                  before(workers[worker1], tiallocs[a2], tiallocs[a1]);

                    else if(a1.resource == worker2 && a1.dmd.id in {7,6,5,4}  &&  a2.dmd.id in {7,6,5,4}  &&  a2.dmd.id < a1.dmd.id )

                                 before(workers[worker2], tiallocs[a1], tiallocs[a2]);

                   …

          In this example, for the worker2, the additional constraint is not applied for the fisrt three units.

          Otherwise, there is always the possibility using the constraint startBeforeStart(a,b,z) where z is the duration of a.

          Best regards,



    ------------------------------
    Thierry Sola
    ------------------------------



  • 5.  RE: Modifying Sched_tasks example

    Posted Thu September 26, 2024 06:04 PM

    Hello Thierry Sola,

    Thanks again!

    I changed the constraint a little bit to match my intended goal.

    forall(a1 in allocs, a2 in allocs: a1.resource == a2.resource)
          if (a2.dmd.id < a1.dmd.id )
          before(workers[a1.resource], tiallocs[a2], tiallocs[a1]);
             else if(a2.dmd.id > a1.dmd.id )
             before(workers[a1.resource], tiallocs[a1], tiallocs[a2]);

    I am trying to make the workers work across the different units sequentially (descending or ascending). So far it was a success. Now, I need to minimize the differences between the Unit IDs in the sequence for all the crews (workers).

    Minimize the sum of (a2.dmd.id - a1.dmd.id) for all workers.

    In other words, for a single task with 3 crews working across 10 units, I need the crew assignment to be something like: Crew1 > units 3, 4, 5, 6 - Crew2 > 2, 1 - Crew3 > 7, 8, 9, 10. Or any other assignment for the crews but following the same rule.

    I would appreciate your help with this one,

    Regards, Fam



    ------------------------------
    Fam Saeed
    ------------------------------



  • 6.  RE: Modifying Sched_tasks example

    Posted Mon September 30, 2024 07:52 AM

    Hello Fam Saeed,

                  Regarding your "before" constraints, they enforce the ascending order for all the resources (workers).

    You can use sequence variables defined for each worker. You define a type for each interval variable tiallocs[a] where a is in allocs. The type of this interval variable should match with the unit of a (the house number associated to this task).           

    This will allow you to know the unit of each task associated with a given worker.

    The function typeOfNext, defined on a sequence variable (seq), returns the type of the interval variable that is next to the interval variable (interval)  in sequence (seq).

    intExpr typeOfNext(sequenceVar seq, intervalVar interval, int lastValue = 0, int absentValue = 0)

    "This function returns an integer expression that represents the type of the interval variable that is next to interval in sequence variable seq. When interval is present and is the last interval of seq, it returns the constant integer value lastValue (zero by default). When interval is absent, it returns the constant integer value absentValue (zero by default)."

    Using this function, it's possible to compute the "differences between the Unit IDs in the sequence for all the crews (workers)".

    int deltaHouse[r in resources][t1 in houseTypes][t2 in houseTypes] =  (abs(t1-t2) <= 1)?0:1;

    where:

    range houseTypes = 0..NbHouses-1;

    int taskType[a in allocs] = (a.dmd.id-1) div NoTasks;

    dvar sequence workers[r in resources] in all(a in allocs: a.resource == r) tiallocs[a] types all(a in allocs: a.resource == r) taskType[a] ;

    At this point you can create an expression that is the sum of all deltaHouse for all workers:

    dexpr int globalDifferences = sum(r in resources, a in allocs : a.resource == r)

    deltaHouse [r][taskType[a]][typeOfNext(workers[r],tiallocs[a],taskType[a],taskType[a])];

    Now, you can minimize this expression.

    There is an example that using this function: CPLEX_Studio2211\opl\examples\opl\sched_tcost.

    Best regards,



    ------------------------------
    Thierry Sola
    ------------------------------



  • 7.  RE: Modifying Sched_tasks example

    Posted Mon April 28, 2025 03:17 PM

    Hello Thierry Sola,

    Thanks so much for all your help.
    I tried the following code:
    range houseTypes = 0..NbHouses-1;
    int taskType[a in allocs] = (a.dmd.id-1) div NoTasks;
    float deltaHouse[r in resources][t1 in houseTypes][t2 in houseTypes] =  (abs(t1-t2) <= 1)?0:abs(t1-t2);
    dvar sequence crews[r in resources] in all(a in allocs: a.resource == r) tiallocs[a] types all(a in allocs: a.resource == r) taskType[a] ;
    dexpr float globalDifferences = sum(r in resources, a in allocs : a.resource == r)
    deltaHouse [r][taskType[a]][typeOfNext(crews[r],tiallocs[a],taskType[a],taskType[a])];
     

    And I added a constraint that globalDifferences == 0
    And an objective function to minimize the total duration.
    It did not work. The solver engine kept trying to find a feasible solution with no success.

    I tried a modified version of this code as follows:

    range houseTypes = 0..NbHouses-1;
    int taskType[a in allocs] = (a.dmd.id-1) div NoTasks;
    float deltaHouse[r in resources][t1 in houseTypes][t2 in houseTypes] =  (abs(t1-t2) <= 1)?0:abs(t1-t2);
    dvar sequence workers[r in resources] in all(a in allocs: a.resource == r) tiallocs[a] types all(a in allocs: a.resource == r) (((a.dmd.id-1) div NoTasks)+1) ; 
    dexpr float globalDifferences = sum(r in resources, a in allocs : a.resource == r)
        deltaHouse [r][taskType[a]][typeOfNext(workers[r],tiallocs[a],taskType[a],taskType[a])];

    However the code worked and the constraint of the global differences was satisfied, but the solution was incorrect. See attached screenshot for the workers:

    I really appreciate your thoughts and help,
    Fam
     



    ------------------------------
    Fam Saeed
    ------------------------------



  • 8.  RE: Modifying Sched_tasks example

    Posted Tue April 29, 2025 10:54 AM

    Hello Fam,

                   It's difficult to answer you without your code. I noticed that the input data has been changed (the task size is smaller). Could you send me your new input data ?

    Best regards,

                   Thierry.



    ------------------------------
    Thierry Sola
    ------------------------------



  • 9.  RE: Modifying Sched_tasks example

    Posted Tue April 29, 2025 12:51 PM

    Hi Thierry,

    Thanks for your prompt response.

    I have sent you a private message with both the .mod and .dat inputs.
    As well, here is the .dat input.

    /*********************************************
     * OPL 22.1.1.0 Data
     * Author: Fam
     * Creation Date: May 14, 2024 at 3:13:06 p.m.
     *********************************************/
     
    NbHouses = 10;
     
    NoTasks = 4;
     
    WorkerNames = {"Crew1-1", "Crew1-2","Crew1-3","Crew2-1","Crew2-2", "Crew3-1", "Crew3-2","Crew4-1", "Crew4-2","Crew4-3","Crew4-4"};
     
    resources = {
      <1, MasonaryLayer,     "Crew1-1" >,
      <2, MasonaryLayer,     "Crew1-2" >,
      <3, MasonaryLayer,     "Crew1-3" >,
      <4, Carpenter,         "Crew2-1" >,
      <5, Carpenter,         "Crew2-2" >,
      <6, Plumber,           "Crew3-1" >,
      <7, Plumber,           "Crew3-2" >, 
      <8, CeilingWorker,     "Crew4-1" >,
      <9, CeilingWorker,     "Crew4-2" >,
      <10, CeilingWorker,     "Crew4-3" >,
      <11, CeilingWorker,     "Crew4-4" >
    };
     
    TaskNames = [ 
      "masonry", 
      "carpentry", 
      "plumbing", 
      "ceiling"
    ];
     
    // Each row represents a building (unit) with duration of tasks in the same order as in TaskNames list
     
    Duration =  [[3, 2, 0, 4],
        [3, 2, 0, 4],
        [3, 2, 3, 4],
        [3, 2, 3, 4],
        [2, 1, 2, 2],
        [6, 4, 6, 8],
        [3, 2, 3, 4],
        [3, 2, 0, 4],
        [3, 2, 0, 4],
        [3, 2, 0, 4]];
     
    requirements = {
      <masonry,      MasonaryLayer,    1>,
      <carpentry,    Carpenter,        1>,
      <plumbing,     Plumber,          1>,
      <ceiling,      CeilingWorker,    1>
    };
     
    // transitionTimes to be entered as a fraction of day (e.g., 2 hrs = 0.25 day)
     
    transitionTimes = [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                       [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                       [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                       [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                       [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                       [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                       [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                       [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                       [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                       [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    ];
     
    ReleaseDate = [  0,     0,   0];
    DueDate     = [120,   212,   304];
    Weight      = [100.0, 100.0, 100.0];
     
    // Format <"Predecessor", "Successor", "Relation type", Lag time>,
    // FS = Finish to Start; SS = Start to Start; FF = Finish to Finish; SF = Start to Finish
    // Lag = + Lag time ; Lead = - Lag time
     
    Precedences = {
      <"masonry",   "carpentry", "FS", 0>,
      <"carpentry", "plumbing", "FS", 0>,
      <"carpentry", "ceiling", "FS", 0>,
      <"plumbing",  "ceiling", "FS", 0>
    };

    Regards,
    Fam


    ------------------------------
    Fam Saeed
    ------------------------------



  • 10.  RE: Modifying Sched_tasks example

    Posted Wed April 30, 2025 05:06 AM

    Hello Fam,

             As you mentioned, you tried first:

    range houseTypes = 0..NbHouses-1;
    int taskType[a in allocs] = (a.dmd.id-1) div NoTasks;
    float deltaHouse[r in resources][t1 in houseTypes][t2 in houseTypes] =  (abs(t1-t2) <= 1)?0:abs(t1-t2);
    dvar sequence crews[r in resources] in all(a in allocs: a.resource == r) tiallocs[a] types all(a in allocs: a.resource == r) taskType[a] ;
    dexpr float globalDifferences = sum(r in resources, a in allocs : a.resource == r)
    deltaHouse [r][taskType[a]][typeOfNext(crews[r],tiallocs[a],taskType[a],taskType[a])];
     

    Here you create "crews" sequence variables instead of "workers". The constraints within the "subject to" part are defined on "workers" sequence variables and not on "crews" sequence variables.

    If you move the definition of the "workers" sequence variables after the definition of the taskType, you can use the previous definition:

    dvar sequence workers[r in resources] in all(a in allocs: a.resource == r) tiallocs[a] types all(a in allocs: a.resource == r) taskType[a] ;   

    Be careful, as I indicated in my first answer: "In your example you set some parameters. For them to be taken into account, you must define them before defining your objective function."

    Best regards,

                   Thierry.



    ------------------------------
    Thierry Sola
    ------------------------------



  • 11.  RE: Modifying Sched_tasks example

    Posted Wed April 30, 2025 10:38 AM

    Thanks so much, Thierry!
    I tried your way, but unfortunately, I still have the same issue. The solver engine keeps processing without reaching any solution.
    Here is the full code

    /*********************************************
     * OPL 22.1.1.0 Model
     * Author: Fam
     * Creation Date: May 17, 2024 at 4:24:01 p.m.
     *********************************************/
    using CP;
    // Initialization of variables
    int NbHouses = ...; // Number of units
    range HousesRange = 1..NbHouses;
    int NoTasks = ...; // Number of tasks
    range TasksRange = 1..NoTasks;
    {string} WorkerNames = ...;  // List of workers' names
    int NoCrews = card(WorkerNames);  // Number of available Workers (Crews)
    range CrewsRange = 1 .. NoCrews;
    string TaskNames[TasksRange] = ...; // List of task names as an array of strings
    {string} ActivityNames = {}; // List of task names as a set of strings
    // Use the data from TaskNames to create the ActivityNames
    execute {
      for (var i in TasksRange) {
        ActivityNames.add(TaskNames[i]);
      }
      writeln("ActivityNames: ", ActivityNames);
    }
    // Create a tuple containing id and name of tasks
    tuple ListOfTask {
      int id;
      string TaskName;
    };
    {ListOfTask} tasksAsTuples = {<(h-1)*NoTasks + i, TaskNames[i]> | i in TasksRange, h in HousesRange};
    execute {
      writeln("tasksAsTuples: ", tasksAsTuples);
    }
    // Create the Workers tuple set with IDs
    tuple WorkerTuple {
      int id;
      string name;
    }
    {WorkerTuple} Workers =  {<id, name> | id in CrewsRange, name in WorkerNames};
    // Duration for each task in each house as a matrix
    int Duration[HousesRange][TasksRange] = ...; 
    // Data for workers
    tuple ResourceDat {
      key int id;
      string type;
      string name;
    };
    {ResourceDat} resources = ...;
    // Create a list of the resource types
    {string} resourceTypes = {r.type | r in resources};
    execute {
      writeln("resources: ", resources);
      writeln("resourceTypes: ", resourceTypes);
    }
    // Data for task & worker requirements (Which crew to complete a task and how many required)
    tuple Requirement {
      string task;
      string resource;
      int quantity;
    };
    {Requirement} requirements = ...;
    execute {
      writeln("requirements: ", requirements);
    }
    // Create a tuple with data of list of tasks & requirements for each one & available crews
    tuple Allocation {
      ListOfTask dmd;
      Requirement req;
      ResourceDat resource;
    };
    {Allocation} allocs = 
      {<tn, m, r> | tn in tasksAsTuples, m in requirements, r in resources : 
       tn.TaskName == m.task && r.type == m.resource};
    execute {
      writeln("allocs: ", allocs);
    }
    // Create a tuple with data of list of tasks & duration to track each resource profile
    tuple TaskWithDuration {
      ListOfTask dmd;
      Requirement req;
      int house;
      int task;
      int duration;
    };
     {TaskWithDuration} tasksWithDuration = {<tn,  m, h, t, Duration[h][t]> 
     | tn in tasksAsTuples, m in requirements,  h in HousesRange, t in TasksRange : tn.TaskName == m.task && tn.id==t};
     
     // Create a tuple with data of list of tasks & duration to track each resource profile
    tuple TaskForWorker {
      ListOfTask dmd;
      Requirement req;
      ResourceDat resource;
      int house;
      int task;
      int duration;
    };
     {TaskForWorker} taskForWorker = {<tn,  m, rc, h, t, Duration[h][t]> 
     | tn in tasksAsTuples, m in requirements,  rc in resources, h in HousesRange, t in TasksRange 
     : tn.TaskName == m.task && tn.id==t && m.resource==rc.type };
    execute {
      writeln("taskForWorker: ", taskForWorker);
    }
    // Relationships among tasks
    tuple Precedence {
      string pre;
      string post;
      string type;
      int delay;
    };
    // Import the relationships from the .dat file
    {Precedence} Precedences = ...;
    execute {
      writeln("Precedences: ", Precedences);
    }
    int ReleaseDate[HousesRange] = ...; 
    int DueDate[HousesRange] = ...; 
    float Weight[HousesRange] = ...; 
    // Define the matrix to import the values
    int transitionTimes[HousesRange][HousesRange] = ...;
    // Transition time across the units
    tuple triplet {
      int loc1;
      int loc2;
      int value;
    };
    // Create a set of tuples from the traveling matrix
    {triplet} transitionAsTuples = {<i, j, transitionTimes[i][j]> | i in HousesRange, j in HousesRange};
    execute {
      writeln("transitionAsTuples: ", transitionAsTuples);
    }
    // Create the house interval variables
    dvar interval houses[h in HousesRange] in ReleaseDate[h]..(maxint div 2)-1;
    // Create the task interval variables
    dvar interval itvs[h in HousesRange][t in TasksRange] size Duration[h][t];
    // Create the allocation interval variables
    dvar interval tiallocs[allocs] optional;
    dvar interval tiops[o in tasksWithDuration] size o.duration;
    execute {
      writeln("tiops: ", tiops);
      writeln("tiallocs: ", tiallocs);
    }
    // Create the sequence variables
    //dvar sequence workers[r in resources] in all(a in allocs: a.resource == r) tiallocs[a] types all(a in allocs: a.resource == r) (((a.dmd.id-1) div NoTasks)+1) ;
    int levels[rt in resourceTypes] = sum(r in resources : r.type == rt) 1;
    cumulFunction cumuls[rt in resourceTypes] =
      sum(rc in requirements, tn in tasksAsTuples : rc.resource == rt && tn.TaskName == rc.task) 
      pulse(itvs[((tn.id-1) div NoTasks) + 1][((tn.id mod NoTasks) == 0 ? NoTasks:(tn.id mod NoTasks))], rc.quantity);
      
    // Define variable to represent crew idle time
    dvar int+ idleTime[w in WorkerNames];
    // Declare the mapping from task names to task indices
    int taskIndex[ActivityNames];
    // Populate the mapping
    execute {
      for (var i in TasksRange) {
        taskIndex[TaskNames[i]] = i;
      }
      writeln("Task index mapping: ", taskIndex);
    }
    // Define the objective Functions:
    // Sequence of units (more as Idle time for crews)
    dexpr float TransitionAcrossUnits = 
      sum(t in TasksRange, w in resources, h in 1..NbHouses-1)
      abs(startOf(itvs[h+1][t]) - endOf(itvs[h][t]));
    // Force Pure sequential order
    range houseTypes = 0..NbHouses-1;
    int taskType[a in allocs] = (a.dmd.id-1) div NoTasks;
    float deltaHouse[r in resources][t1 in houseTypes][t2 in houseTypes] =  (abs(t2-t1) == 1)?0:abs(t1-t2);
    dvar sequence workers[r in resources] in all(a in allocs: a.resource == r) tiallocs[a] types all(a in allocs: a.resource == r) taskType[a] ;     
    dexpr float globalDifferences = sum(r in resources, a in allocs : a.resource == r)
    deltaHouse [r][taskType[a]][typeOfNext(workers[r],tiallocs[a],taskType[a],taskType[a])];
    // The total duration
    dexpr float TotalDuration = max(h in HousesRange, t in TasksRange) endOf(itvs[h][t]);
    execute {
      writeln("TotalDuration expression created: ", TotalDuration);
    }
    // Determine the earliest start time among all houses
    dexpr float firstStartTime = min(h in HousesRange) startOf(houses[h]);
    /* The total cost
    dexpr float TotalCost = sum(h in HousesRange)
      (Weight[h] * maxl(0, endOf(houses[h])-DueDate[h]) + lengthOf(houses[h]));
    */
    // The Crews Idle time
    dexpr float TotalIdleTime = sum(w in WorkerNames) idleTime[w];
    // Add multi-objective function*/ 
    //minimize staticLex (TotalDuration, globalDifferences);
    // Alternative Objective Functions:
     minimize TotalDuration;
    // minimize globalDifferences;
    // minimize sequentialOrder;
    // minimize sumUnitDifferences;
    // minimize TransitionAcrossUnits;
    // minimize TotalDuration + TotalIdleTime;
    // minimize TotalIdleTime;
    // minimize TotalCost;
    subject to {
    // Ensure the first task in the first house starts at day 0
    //startOf(itvs[1, 1]) == 0;  
    firstStartTime == 0;
    globalDifferences == 0;
    // Add the precedence constraints
    forall(h in HousesRange)
      forall(p in Precedences) 
        if (p.type == "FS") endBeforeStart(itvs[h][taskIndex[p.pre]], itvs[h][taskIndex[p.post]], p.delay);
        else if (p.type == "SS") startAtStart(itvs[h][taskIndex[p.pre]], itvs[h][taskIndex[p.post]], p.delay);
        else if (p.type == "FF") endAtEnd(itvs[h][taskIndex[p.pre]], itvs[h][taskIndex[p.post]], p.delay);
        else if (p.type == "SF") startBeforeEnd(itvs[h][taskIndex[p.pre]], itvs[h][taskIndex[p.post]], p.delay);
    // Add the house span constraints
    forall(h in HousesRange)
    span(houses[h], all(t in TasksRange) itvs[h][t]);
    forall(h in HousesRange) {
      forall(t in TasksRange) {
        forall (rc in requirements : rc.task == TaskNames[t]) {
                 alternative(itvs[h][t], all(a in allocs : a.dmd.id == (h-1)*NoTasks+t) tiallocs[a], rc.quantity);
       }        
      }
    // Add the no overlap constraints
    forall(r in resources) {
      noOverlap(workers[r], transitionAsTuples);
    }
    // Ensure resource levels are not exceeded
    forall(rt in resourceTypes) cumuls[rt] <= levels[rt];
    }
    // Print intermediate results
    execute {
      writeln("Constraints added.");
    }
    // Set solver parameters
    execute {
      cp.param.FailLimit = 10000;
      cp.param.TimeLimit = 300;
     // cp.param.TimeMode = "CPUTime";
     cp.param.SearchType = "auto";
      writeln("Solver parameters set.");
    }

    I really appreciate your thoughts on how to fix this.
    Best regards,
    Fam


    ------------------------------
    Fam Saeed
    ------------------------------



  • 12.  RE: Modifying Sched_tasks example

    Posted Wed April 30, 2025 10:59 AM

    Hello Fam,

          As I mentioned: Be careful, as I indicated in my first answer: "In your example you set some parameters. For them to be taken into account, you must define them before defining your objective function." 

    The first solution is found in less than one second.

    Best regards,

         Thierry.



    ------------------------------
    Thierry Sola
    ------------------------------



  • 13.  RE: Modifying Sched_tasks example

    Posted Wed April 30, 2025 11:19 AM

    I didn't notice that you were talking about the solver parameters. Now I get it.
    I have one followup question.
    The house type values are zero-based (houses Ids range 0...9)(see screenshot). Is there any way to get them back to one-based values (houses IDs range 1... 10).

    Much appreciated,
    Fam



    ------------------------------
    Fam Saeed
    ------------------------------



  • 14.  RE: Modifying Sched_tasks example

    Posted Wed April 30, 2025 11:38 AM

    Fam,

           Yes, you can define the "range houseTypes = 1..NbHouses;" instead of 0..NbHouses-1. But you need to update the taskType (int taskType[a in allocs] = ((a.dmd.id-1) div NoTasks)+1;).

    Best regards,

         Thierry.



    ------------------------------
    Thierry Sola
    ------------------------------



  • 15.  RE: Modifying Sched_tasks example

    Posted Wed April 30, 2025 11:49 AM

    You are the best, Thierry!

    Regards,
    Fam



    ------------------------------
    Fam Saeed
    ------------------------------



  • 16.  RE: Modifying Sched_tasks example

    Posted Thu May 15, 2025 06:15 PM

    Hi Thierry,
    I defined a String ObjFunction = ...;
    where 
    // Alternative Objective Functions:
    if (ObjFunction == "TD") minimize TotalDuration;
    else if (ObjFunction == "GD") minimize globalDifferences;
    else if (ObjFunction == "TAU") minimize TransitionAcrossUnits;
    else if (ObjFunction == "TI") minimize TotalIdleTime;
    else if (ObjFunction == "TC") minimize TotalCost;

    From what I found online, this can not be formulated this way. As the if function is only allowed as part of the constraints Subject to {

    Any thoughts on this.
    Many thanks,
    Fam



    ------------------------------
    Fam Saeed
    ------------------------------



  • 17.  RE: Modifying Sched_tasks example

    Posted Fri May 16, 2025 04:05 AM

    Hello Fam,

          Here is a possible solution:

    minimize (ObjFunction == "TD" ? TotalDuration:

                   (ObjFunction == "GD" ?  globalDifferences:

                    (ObjFunction == "TAU" ? TransitionAcrossUnits:

                    (ObjFunction == "TI"    ? TotalIdleTime: TotalCost))));

    Best regards,



    ------------------------------
    Thierry Sola
    ------------------------------



  • 18.  RE: Modifying Sched_tasks example

    Posted Thu May 22, 2025 03:30 PM

    Hello Thierry,

    I really appreciate all your help.

    I am trying to calculate the idle time of crews (time elapsed while crews are not working in any units) and exclude the transition time the crews take between the units. My code is as follows:

    dexpr float idleTime[r in resources] = 
      sum(a in allocs : a.resource == r)
        maxl(0, startOf(tiallocs[a]) - endOfPrev(workers[r], tiallocs[a], taskType[a], taskType[a]));
    dexpr float TotalIdleTime = sum(r in resources) idleTime[r];

    It mis-calculates the idletime, and I still need to exclude the transition time from these calculations.

    Also, I need to calculate the transition time that all crews experienced during the project. 

    Thanks so much,
    Fam



    ------------------------------
    Fam Saeed
    ------------------------------



  • 19.  RE: Modifying Sched_tasks example

    Posted Fri May 23, 2025 06:03 AM

    Hello Fam,

    dexpr float idleTime[r in resources] = 
      sum(a in allocs : a.resource == r)
        maxl(0, startOf(tiallocs[a]) - endOfPrev(workers[r], tiallocs[a], taskType[a], taskType[a]));

    If tiallocs[a] is the first interval in the sequence workers[r], endOfPrev(workers[r], tiallocs[a], taskType[a], taskType[a]) = taskType[a].

    In this case, if startOf(tiallocs[a]) is strictly greater than taskType[a], then the difference startOf(tiallocs[a]) - taskType[a] is taken into account in idleTime[r], whereas it should not. 

    You must replace the first taskType[a] (third argument of endOfPrev) by a value greater than your horizon.

    Now if you need to exclude the transition time you must subtract the transition time transitionTime[typeOfPrev(worker[r], tiallocs[a], 0, 0)][ type[a].

    In this case, all the transitionTime that contains 0 (transitionTime[0][?] = transitionTime[?][0]) must be null.

    You are not required to use 0, you can use a value of a type that is not used.

    Best regards,



    ------------------------------
    Thierry Sola
    ------------------------------