MILP constraint for cyclic task scheduling - mixed-integer-programming

I am working on developing a MILP mathematical model which deals with cyclic tasks. I at a stage where I have to design constraints in this regard. here is the simplified version of the problem.
the are 4 tasks of A type: [A1, A2, A3, A4]. I need a constraint that
first make sure the tasks are in order
second, in the planning horizon, after task A4 is again A1. this cycle will go on till the planning horizon ends.
I have created constraints to verify the sequence of tasks:
X_A: = 1 if task A is being done
index a: tasks {1, 2, .... , A}
sum over planning horizon(X_a) >= sum over planning horizon(X_a+1) for all a in {1,2, ... A-1}
I am stuck on writing a constraint to make sure within the planning horizon after the last task it goes and start the first task and repeat the cycle.

I have used Google_ortool's CP-SAT solver (python API) to solve the problem.
So if your total horizon length where you need to plan the task = 51 units,
and your tasks are tasks = {"a1" : 2, "a2" : 3, "a3" : 7, "a4" : 9, "a5" : 1} where "a1" is task and 2 is the length of the task, then solution will look like :
(cycle, task ==> whether task was performed ==> task duration)
(0, 'a1') ==> 1 ==> 2
(0, 'a2') ==> 1 ==> 3
(0, 'a3') ==> 1 ==> 7
(0, 'a4') ==> 1 ==> 9
(0, 'a5') ==> 1 ==> 1
(1, 'a1') ==> 1 ==> 2
(1, 'a2') ==> 1 ==> 3
(1, 'a3') ==> 1 ==> 7
(1, 'a4') ==> 1 ==> 9
(1, 'a5') ==> 1 ==> 1
(2, 'a1') ==> 1 ==> 2
(2, 'a2') ==> 1 ==> 3
(2, 'a3') ==> 0 ==> 0
(2, 'a4') ==> 0 ==> 0
(2, 'a5') ==> 0 ==> 0
total length = 49, which is less than the horizon length 51, we
cant take up a3 because its length is 7 and 49 + 7 >= 51. So we
stop at a2 only.
code listing in python
from ortools.sat.python import cp_model as cp
import numpy as np
planning_horizon_length = 51
# tasks and their corresponding duration
tasks = {"a1" : 2,
"a2" : 3,
"a3" : 7,
"a4" : 9,
"a5" : 1
}
total_task_length = sum(tasks.values())
# how many cycles we would require
num_cycles = int(np.ceil(planning_horizon_length / total_task_length))
model = cp.CpModel()
# decision variable : for each cycle - task whether it will be done or not
cycle_tasks = []
dv_cycle_task = {}
for i in range(num_cycles):
for j in tasks:
dv_cycle_task[(i, j)] = model.NewBoolVar("cycle : " + str(i) + " & " + "task : " + str(j))
cycle_tasks.append((i, j))
# precedence constraint : for each cycle, a1 should come before a2 and so on
for i, j in enumerate(cycle_tasks):
if i < len(cycle_tasks) - 1:
model.Add(dv_cycle_task[cycle_tasks[i]] >= dv_cycle_task[cycle_tasks[i + 1]])
# capture total run length
total_run_length = model.NewIntVar(0, planning_horizon_length, "")
model.Add(total_run_length == sum(dv_cycle_task[(i, j)] * tasks[j] for (i, j) in cycle_tasks))
# total run length should be lower than planning_horizon_length
model.Add(total_run_length <= planning_horizon_length)
# we want total run length should be as close to planning_horizon_length i.e. 51
model.Maximize(total_run_length - planning_horizon_length)
solver = cp.CpSolver()
solver.Solve(model)
# objective function value : how extra we went
solver.Value(total_run_length)
# inspect the solution
for (i, j) in cycle_tasks:
print(str((i, j)) + " ==> " + str(solver.Value(dv_cycle_task[(i, j)])) + " ==> " + str(solver.Value(dv_cycle_task[(i, j)]) * tasks[j]))

Related

Get a sequence number from 0 and alternate positive/negative incrementing every other time

I would like to be able to obtain a (non-convergent) sequence of numbers by a simple calculation that would look like this: 0, 1, -1, 2, -2, 3, -3, 4, -4 ...
By simple calculation I mean being able to do it with a single variable that would start from 1 (or 0) without having to rearrange this sequence.
I made several (unsuccessful) attempts in Lua, here is what it should look like in principle (this example only alternates 0s and 1s):
do
local n = 0
for i = 1, 10 do print(n)
n = n==0 and 1 or -n + (n/n)
end
end
Is this possible and how?
Update:
I just succeeded like this:
local n, j = 0, 2
for i = 1, 10 do print(n)
n = n==0 and 1 or j%2==0 and -(n+(n/math.abs(n))) or -n
j = j + 1
end
But I have to help myself with a second variable, I would have liked to know if with only n it would be possible to do it?
The whole numbers are enumerable. Thus there exists a mapping from the natural numbers to whole numbers. You'll now have to use a loop to loop over natural numbers, then compute a function that gives you a whole number:
-- 0, 1...10, -1...-10 -> 21 numbers total
for n = 1, 21 do
local last_bit = n % 2
local sign = 1 - (2 * last_bit)
local abs = (n - last_bit) / 2
print(sign * abs)
end
prints
-0
1
-1
2
-2
...
10
-10
on Lua 5.1; on newer Lua versions, you can use n // 2 instead of (n - last_bit) / 2 to (1) use ints and (2) make extracting the abs cheaper.
Simply "emit" both n and -n in each iteration:
for n = 0, 10 do
print(n)
print(-n)
end
My problem was solved by #EgorSkriptunoff in comment of my question, his approach is:
n = (n > 0 and 0 or 1) - n
The output of:
local n = 0
for i=1,10 do io.write(n..", ")
n = (n > 0 and 0 or 1) - n
end
Actually gives:
0, 1, -1, 2, -2, 3, -3, 4, -4, 5,

Sqlite INSERT INTO with a group_concat which contains a subselect

I need to take data and combine to insert into an table as a menu code. The menu code takes the form LNNLLLL (L = Letter and N=Number) and it is made up of MenU Cycle (hard-coded, one off project), Week Number (1-3), Day Number (1-7 - Monday=1), MealCode, Customer Type Code (3 Letters). The following SELECT query returns exactly what I want insert. The hardcoded date is a known date from which the week number and day number can be derived ie it is a Week 1 Monday.
SELECT group_concat('C' || CASE
WHEN (CAST(julianday(mealdate) AS INT) - cast(julianday('2022-08-22') AS INT)) % 21 <= 6 THEN 1
WHEN (CAST(julianday(mealdate) AS INT) - cast(julianday('2022-08-22') AS INT)) % 21 <= 13 THEN 2
ELSE 3
END ||
CASE WHEN (CAST(julianday(mealdate) AS INT) - cast(julianday('2022-08-22') AS INT)) % 21 + 1 IN (1, 8, 15) THEN 1
WHEN (CAST(julianday(mealdate) AS INT) - cast(julianday('2022-08-22') AS INT)) % 21 + 1 IN (2, 9, 16) THEN 2
WHEN (CAST(julianday(mealdate) AS INT) - cast(julianday('2022-08-22') AS INT)) % 21 + 1 IN (3, 10, 17) THEN 3
WHEN (CAST(julianday(mealdate) AS INT) - cast(julianday('2022-08-22') AS INT)) % 21 + 1 IN (4, 11, 18) THEN 4
WHEN (CAST(julianday(mealdate) AS INT) - cast(julianday('2022-08-22') AS INT)) % 21 + 1 IN (5, 12, 19) THEN 5
WHEN (CAST(julianday(mealdate) AS INT) - cast(julianday('2022-08-22') AS INT)) % 21 + 1 IN (6, 13, 20) THEN 6
WHEN (CAST(julianday(mealdate) AS INT) - cast(julianday('2022-08-22') AS INT)) % 21 + 1 IN (7, 14, 21) THEN 7
END ||
MealCode ||
(SELECT CustTypeCodeNew FROM CustomerNew WHERE CustCodeNew LIKE SalesOrderNew.CustomerCode)) AS MenuCode
FROM SalesOrderNew
GROUP BY SalesOrderID
Here is some output from that (I've added MealDate and MealCode to the select just to give better context to the resulting MenuCode column which is the one that needs inserted.
MealDate MealCode MenuCode
2022-08-23 K C12KNRE
2022-08-23 K C12KRES
2022-08-23 K C12KSHO
2022-08-23 K C12KRES
2022-08-25 T C14TNRE
2022-08-25 T C14TRES
2022-08-25 L C14LNRE
2022-08-25 L C14LNRE
2022-08-25 T C14TNRE
The MenuCode column is currently filled with NULL values.
However if I put add a line above to INSERT INTO SalesOrderNew (MenuCode) I get this error Result: NOT NULL constraint failed: SalesOrderNew.CustomerCode. It does not seem to like the subselect that used within the group_contact. How do I go about resolving this?

How to dynamically compute nurse walking distance based on assignments in a nurse-patient scheduler model in Pyomo?

I am writing a Nurse-Patient assignment algorithm in Pyomo, but I am having trouble dynamically computing the walking distance between assigned patient beds for each nurse. The relevant sets and variables are below with a small example. The variables are filled in with the values that I would want at the end of solving.
#Sets
model.PatientIDs = {0, 1, 2}
model.NurseIDs = {a, b}
#Indexed parameter (indexed by (patient1, patient2))
# Stores the distance between each patient bed
model.Bed_Distances = {(0, 0) : 0, (0, 1) : 6.4, (0, 2) : 7.2, (1, 0) : 6.4, (1, 1) : 0, (1, 2) : 1.9, (2, 1) : 1.9), (2, 0) : 7.2}
# Indexed variable (indexed by (patient, nurse))
model.Assignments = {(0, a): 1, (0, b): 0, (1, a) : 0, (1, b) : 1, (2, a) : 1, (2, b) : 0}
# Indexed variable (indexed by nurse)
# Keeps track of the distance between assigned patient beds for each nurse
model.Nurse_Distances = {a : 7.2, b : 0}
I'm having trouble computing the nurse distances dynamically as the model is being solved (since it is dependent on the patients assigned to each nurse). Since the model.ASSIGNMENTS decision variable is represented with binary 0 or 1, I've been using this rule to compute the nurse distances:
def nurse_distance(self, nurse):
return model.NURSE_DISTANCE[nurse] == sum([model.ASSIGNMENTS[p1, nurse] * model.ASSIGNMENTS[p2, nurse] * model.BED_DISTANCES[p1, p2] for p1 in model.PATIENTS for p2 in model.PATIENTS])
model.DISTANCE_CONSTRAINT = pe.Constraint(model.NURSES, rule = nurse_distance)
After adding this constraint, my model runs forever, or when I run it with only 2 or 3 patients it finishes relatively quickly and gives me an error that looks like this:
ERROR: Error parsing NEOS solution file NEOS log: Job 11992161 dispatched password: iGYfJtMj ---------- Begin Solver Output ----------packages/pyomo/opt/plugins/sol.py", line 87, in _load raise ValueError("no Options line found") ValueError: no Options line found
Is there a better way to keep track of a variable that is dependent on the value of another variable in Pyomo (nurse bed distances is dependent on the assignments made between nurses and patients)? If not, is there a more computationally efficient way to compute it within the constraint I've written without using "if then" statements?
You are creating a non-linear model by multiplying your decision variables together, which is probably not desired...
Because you have an "and" condition here (that the same nurse is assigned to p1 and p2) you almost certainly need another binary "helper" variable to track whether both are true, then use that. Of course, you will need a linking constraint between the individual assignments and the paired assignment. See the example below... Note that "Todd" gets off easy here because he only has 1 patient.
Code:
# Nurse Patrol
import pyomo.environ as pyo
# data
PatientIDs = [0, 1, 2]
NurseIDs = ['Todd', 'Cindy']
Bed_Distances = {(0, 0) : 0, (0, 1) : 6.4, (0, 2) : 7.2, (1, 0) : 6.4, (1, 1) : 0, (1, 2) : 1.9, (2, 1) : 1.9, (2, 0) : 7.2}
patient_pairings = [(p1, p2) for p1 in PatientIDs for p2 in PatientIDs if p1 != p2]
# model
m = pyo.ConcreteModel('Nurse Patrol')
# put the stuff above into model parameters. You can use them "raw" but this will help you T/S the model
# SETS
m.N = pyo.Set(initialize=NurseIDs)
m.P = pyo.Set(initialize=PatientIDs)
m.PP = pyo.Set(initialize=patient_pairings)
# PARAMS
m.distance = pyo.Param(m.P, m.P, initialize=Bed_Distances)
# VARIABLES
m.assign = pyo.Var(m.N, m.P, domain=pyo.Binary)
m.pair_assigned = pyo.Var(m.N, m.PP, domain=pyo.Binary) # true if BOTH are assigned to nurse
# OBJ: Minimize the total of all nurses' travels
m.obj = pyo.Objective(expr=sum(m.pair_assigned[n, p1, p2]*m.distance[p1, p2]
for (p1, p2) in m.PP
for n in m.N))
# CONSTRAINTS
# Cover all patients
def covered(m, patient):
return sum(m.assign[n, patient] for n in m.N) >= 1
m.C1 = pyo.Constraint(m.P, rule=covered)
# link the assignment to the paired assignment
def paired(m, n, p1, p2):
return m.pair_assigned[n, p1, p2] >= m.assign[n, p1] + m.assign[n, p2] - 1
m.C2 = pyo.Constraint(m.N, m.PP, rule=paired)
result = pyo.SolverFactory('glpk').solve(m)
print(result)
m.assign.display()
for n in m.N:
tot_dist = sum(m.pair_assigned[n, p1, p2]*m.distance[p1, p2] for (p1, p2) in m.PP)
print(f'nurse {n} distance is: {pyo.value(tot_dist)}')
Yields:
Problem:
- Name: unknown
Lower bound: 3.8
Upper bound: 3.8
Number of objectives: 1
Number of constraints: 16
Number of variables: 19
Number of nonzeros: 43
Sense: minimize
Solver:
- Status: ok
Termination condition: optimal
Statistics:
Branch and bound:
Number of bounded subproblems: 3
Number of created subproblems: 3
Error rc: 0
Time: 0.007218837738037109
Solution:
- number of solutions: 0
number of solutions displayed: 0
assign : Size=6, Index=assign_index
Key : Lower : Value : Upper : Fixed : Stale : Domain
('Cindy', 0) : 0 : 0.0 : 1 : False : False : Binary
('Cindy', 1) : 0 : 1.0 : 1 : False : False : Binary
('Cindy', 2) : 0 : 1.0 : 1 : False : False : Binary
('Todd', 0) : 0 : 1.0 : 1 : False : False : Binary
('Todd', 1) : 0 : 0.0 : 1 : False : False : Binary
('Todd', 2) : 0 : 0.0 : 1 : False : False : Binary
nurse Todd distance is: 0.0
nurse Cindy distance is: 3.8
[Finished in 558ms]

Collatz sequence in Erlang

The problem that I am trying to solve is as follows:
Write an Erlang function named collatz that takes one argument N. You may assume that N is an integer 1 or larger. The function should print the Collatz sequence (one number per line) starting with N. For example, collatz( 4 ) should print 4, 2, 1 (on separate lines). collatz( 6 ) should print 6, 3, 10, 5, 16, 8, 4, 2, 1 (on separate lines).
The collatz function that I have written is working properly but I am having difficulty in printing the output on separate lines. The commented-out part of the code below is my attempt to generate the output on separate lines.
collatz(1) -> [1];
collatz(N) when N rem 2 == 0 ->
[N|collatz(N div 2)];
%[io:format("Collatz is : ~p~n",[N])N|collatz(N div 2)];
collatz(N) ->
[N|collatz(3*N+1)].
%[io:format("Collatz is : ~p~n",[N])N|colla[N|collatz(N div 2)]tz(3*N+1)].
The output that I get when I call for example collatz(5) is [5,16,8,4,2,1]. I want these numbers to be printed out on separate lines.
Instead of running the whole program and printing the result, consider printing each element before each iteration, like
collatz(N) -> io:format("~p~n", [N]), collatz(next_collatz(N)).
You just need to evaluate io:format/2 before prepending N in your list…
collatz(1) ->
io:format("Collatz is : 1~n"),
[1];
collatz(N) when N rem 2 == 0 ->
io:format("Collatz is : ~p~n", [N]),
[N | collatz(N div 2)];
collatz(N) ->
io:format("Collatz is : ~p~n", [N]),
[N | collatz(3 * N + 1)].
1> C = fun C(1,_) -> io:format("1~n") ;
2> % rem is not allowed in a guard, it is why I added it in the parameters
2> C(N,0) -> io:format("~p~n",[N]), NN = N div 2, C(NN, NN rem 2);
3> C(N,_) -> io:format("~p~n",[N]), NN = 3 * N + 1, C(NN, NN rem 2) end.
#Fun<erl_eval.19.97283095>
4> Collatz = fun(N) -> C(N, N rem 2) end.
#Fun<erl_eval.44.97283095>
5> Collatz(5).
5
16
8
4
2
1
ok
6>

How to determine the number of possible combinations of letters that contain a degenerate substring

I've been racking my brain for a couple of days to work out a series or closed-form equation to the following problem:
Specifically: given all strings of length N that draws from an alphabet of L letters (starting with 'A', for example {A, B}, {A, B, C}, ...), how many of those strings contain a substring that matches the pattern: 'A', more than 1 not-'A', 'A'. The standard regular expression for that pattern would be A[^A][^A]+A.
The number of possible strings is simple enough: L^N . For small values of N and L, it's also very practical to simply create all possible combinations and use a regular expression to find the substrings that match the pattern; in R:
all.combinations <- function(N, L) {
apply(
expand.grid(rep(list(LETTERS[1:L]), N)),
1,
paste,
collapse = ''
)
}
matching.pattern <- function(N, L, pattern = 'A[^A][^A]+A') {
sum(grepl(pattern, all.combinations(N, L)))
}
all.combinations(4, 2)
matching.pattern(4, 2)
I had come up with the following, which works for N < 7:
M <- function(N, L) {
sum(
sapply(
2:(N-2),
function(g) {
(N - g - 1) * (L - 1) ** g * L ** (N - g - 2)
}
)
)
}
Unfortunately, that only works while N < 7 because it's simply adding the combinations that have substrings A..A, A...A, A....A, etc. and some combinations obviously have multiple matching substrings (e.g., A..A..A, A..A...A), which are counted twice.
Any suggestions? I am open to procedural solutions too, so long as they don't blow up with the number of combinations (like my code above would). I'd like to be able to compute for values of N from 15 to 25 and L from 2 to 10.
For what it is worth, here's the number of combinations, and matching combinations for some values of N and L that are tractable to determine by generating all combinations and doing a regular expression match:
N L combinations matching
-- - ------------ --------
4 2 16 1
5 2 32 5
6 2 64 17
7 2 128 48
8 2 256 122
9 2 512 290
10 2 1024 659
4 3 81 4
5 3 243 32
6 3 729 172
7 3 2187 760
8 3 6561 2996
9 3 19683 10960
10 3 59049 38076
4 4 256 9
5 4 1024 99
6 4 4096 729
7 4 16384 4410
8 4 65536 23778
9 4 262144 118854
10 4 1048576 563499
It is possible to use dynamic programming approach.
For fixed L, let X(n) be number of strings of length n that contain given pattern, and let A(n) be number of strings of length n that contain given pattern and starts with A.
First derive recursion formula for A(n). Lets count all strings in A(n) by grouping them by first 2-3 letters. Number of strings in A(n) with:
"second letter A" is A(n-1),
"second letter non-A and third letter is A" is A(n-2),
"second and third letter non-A" is (L^(n-3) - (L-1)^(n-3)). That is because string 'needs' at least one A in remaining letters to be counted.
With that:
A(n) = A(n-1) + (L-1) * (A(n-2) + (L-1) * (L^(n-3) - (L-1)^(n-3)))
String of length n+1 can start with A or non-A:
X(n+1) = A(n+1) + (L-1) * X(n)
X(i) = A(i) = 0, for i <= 3
Python implementation:
def combs(l, n):
x = [0] * (n + 1) # First element is not used, easier indexing
a = [0] * (n + 1)
for i in range(4, n+1):
a[i] = a[i-1] + (l-1) * (a[i-2] + (l-1) * (l**(i-3) - (l-1)**(i-3)))
x[i] = a[i] + (l-1) * x[i-1]
return x[4:]
print(combs(2, 10))
print(combs(3, 10))
print(combs(4, 10))
This can be described as a state machine. (For simplicity, x is any letter other than A.)
S0 := 'A' S1 | 'x' S0 // ""
S1 := 'A' S1 | 'x' S2 // A
S2 := 'A' S1 | 'x' S3 // Ax
S3 := 'A' S4 | 'x' S3 // Axx+
S4 := 'A' S4 | 'x' S4 | $ // AxxA
Counting the number of matching strings of length n
S0(n) = S1(n-1) + (L-1)*S0(n-1); S0(0) = 0
S1(n) = S1(n-1) + (L-1)*S2(n-1); S1(0) = 0
S2(n) = S1(n-1) + (L-1)*S3(n-1); S2(0) = 0
S3(n) = S4(n-1) + (L-1)*S3(n-1); S3(0) = 0
S4(n) = S4(n-1) + (L-1)*S4(n-1); S4(0) = 1
Trying to reduce S0(n) to just n and L gives a really long expression, so it would be easiest to calculate the recurrence functions as-is.
For really large n, this could be expressed as a matrix expression, and be efficiently calculated.
n
[L-1 1 0 0 0 ]
[ 0 1 L-1 0 0 ] T
[0 0 0 0 1] × [ 0 1 0 L-1 0 ] × [1 0 0 0 0]
[ 0 0 0 L-1 1 ]
[ 0 0 0 0 L ]
In JavaScript:
function f(n, L) {
var S0 = 0, S1 = 0, S2 = 0, S3 = 0, S4 = 1;
var S1_tmp;
while (n-- > 0) {
S0 = S1 + (L - 1) * S0;
S1_tmp = S1 + (L - 1) * S2;
S2 = S1 + (L - 1) * S3;
S3 = S4 + (L - 1) * S3;
S4 = S4 + (L - 1) * S4;
S1 = S1_tmp;
}
return S0;
}
var $tbody = $('#resulttable > tbody');
for (var L = 2; L <= 4; L++) {
for (var n = 4; n <= 10; n++) {
$('<tr>').append([
$('<td>').text(n),
$('<td>').text(L),
$('<td>').text(f(n,L))
]).appendTo($tbody);
}
}
#resulttable td {
text-align: right;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<table id="resulttable">
<thead>
<tr>
<th>N</th>
<th>L</th>
<th>matches</th>
</tr>
</thead>
<tbody>
</tbody>
</table>

Resources