Best approach to calculate if required dose possible from multiple different stock sizes

Thicko

Registered User.
Local time
Today, 12:07
Joined
Oct 21, 2011
Messages
61
We have a manual process where if a 250mg dose of Drug A is required, we give 120mg + 75mg + 55mg as 3 separate doses, or (2 x 75mg) + 100mg, or (2 x 80mg) + 90mg

What I'm after is the best way to calculate if the available dose is divisible by any combination of 120mg, 100mg, 90mg, 80mg, 75mg, 55mg (will vary depending on Drug choice)

My current approach would be to Loop through the available doses till it equals the required dose starting with:
  • 1 of smallest size then loop through 1 of remaining sizes
  • 1 of second smallest size then loop through 1 of remaining sizes
  • etc...
  • Then 2 of smallest size & loop through 1 of remaining sizes
  • and so on.. till we get to max of 3 separate doses of any combination.

I can put some rules in if only 1 in stock of that size then no need to run for 2.

My worry is although a quick calculation for a human to work out the loops are going to make this a very slow bit of code that I really need to be a quick check if stock available.

Many thanks in advance for any insight into the best approach to this issue.

Thicko
 
Concerned about the logic: I would think the process would be

Target = Dosage total, Pack = null, Diff = Target
For the selected Drug, while DIFF <> 0,
Select Drug AvailableDose = or < Diff
Pack = Drug AvailableDose + Pack
Diff = Target - AvailableDose
If Diff < 0 then AvailableDose = stepdown to next smallest
Next (Drug Dose)
Pack = string listing tablets to make target

Of course this does not consider stock levels, max 3 combos, and other constraints (?).
Advantage: Minimises the number of tablets. If you always start from the smallest, then you will inevitably be discarding valid combos where combos exceed 3 before arriving at the target.

Re speed - you will need to ensure an appropriate index exists on your table involving the Drug and the AvailableDose. Presume the table of drug and tablet exists (not a column for each available dose).
 
Last edited:
I like to solve such tasks by query. After all, you ask your question in a database forum.
Create a table with a column dose and write your values in it.

A CROSS JOIN is created in a query and all conceivable variants of combinations are determined, which are then restricted to a meaningful overview by conditions.
SQL:
SELECT
   T1.dose AS D1,
   T2.dose AS D2,
   T3.dose AS D3,
   T1.dose + T2.dose + T3.dose AS [Sum]
FROM
   tblDoses AS T1,
   tblDoses AS T2,
   tblDoses AS T3
WHERE
   T1.dose + T2.dose + T3.dose <= 250
      AND
   T1.dose <= T2.dose
      AND
   T2.dose <= T3.dose
ORDER BY
   4 DESC,
   1,
   2,
   3
You can rephrase designations freely.
 
Wouldn't you also consider the fact that a 500mg pill could be cut in half (if it's a solid cuttable pill)? So checking for the largest dosage available and seeing if the target equals half should be in there.
 
Your loop should start with the largest dose < the requested amt.
Use mod as your division operator to get an integer,
 
We have a manual process where if a 250mg dose of Drug A is required, we give 120mg + 75mg + 55mg as 3 separate doses, or (2 x 75mg) + 100mg, or (2 x 80mg) + 90mg

What I'm after is the best way to calculate if the available dose is divisible by any combination of 120mg, 100mg, 90mg, 80mg, 75mg, 55mg (will vary depending on Drug choice)

My current approach would be to Loop through the available doses till it equals the required dose starting with:
  • 1 of smallest size then loop through 1 of remaining sizes
  • 1 of second smallest size then loop through 1 of remaining sizes
  • etc...
  • Then 2 of smallest size & loop through 1 of remaining sizes
  • and so on.. till we get to max of 3 separate doses of any combination.

I can put some rules in if only 1 in stock of that size then no need to run for 2.

My worry is although a quick calculation for a human to work out the loops are going to make this a very slow bit of code that I really need to be a quick check if stock available.

Many thanks in advance for any insight into the best approach to this issue.

Thicko

Believe or not, this is similar to problems in queueing theory and also to problems in load optimization for shipping.

FIRST, you have to decide not only the primary goal (total dose) but the secondary goal (which can differ wildly). For secondary goals: Do you want to do the dosage with the fewest separate dose sizes, or the smallest separate dose sizes, ... and you have a list of available sizes, so you need to stay within fixed dose sizes. Other questions you need to ask include: What do you do when you cannot reach the target using three of the largest doses? What will you do when no combination of the available doses can reach the target exactly?

It might be possible to compute this using recursion - by trying to pick the LARGEST dose that is smaller than the target first, and then subtracting that dose size from the target and treating it as a new problem for a smaller target. (That "treat the remainder as a new problem with a smaller target" is the recursion step.) Along the steps of the selection, you accumulate the doses selected by previous iterations. This would give you the selected dose in larger units, but you would have to also take into account the quantity on hand for each selected dose to see if the allocation could even be made.

Because you have multiple ways to select combinations, you will find that this automated method would tend to not give you some dose combinations that you would have preferred that it choose. The trick there is to somehow quantify WHY you would or would not have made the choice. Because you have so many options, all I can do is offer the insight. This would be a very complex problem and anything I try to code for you would make a lot of assumptions that wouldn't fit into your situation.
 
Doc what about calculating different options that the user can then select? I agree though, this is quite a complex problem to code.
 
Doc what about calculating different options that the user can then select? I agree though, this is quite a complex problem to code.

Mike, the sky is the limit with possibilities, your imagination is the limit with implementation. I only know of two basic approaches for this class of problem - smallest first or largest first. "Random" first is in that part of queueing theory that I never got to. But the restrictions on "max 3 doses" and "availability of doses" will make this a tricky combination. I will say this: Biggest Doses First would tend to minimize the count of doses and be the best way to assure that you are within the 3 dose limit. Smallest Doses First would likely tend to trigger recomputes because it would tend to use too many smaller doses and hit that 3 dose limit.

And to other members, several of you offered a similar approach that would essentially be the same as recursion. I may just be the only one who used that word.
 
Thank you all, got a lot to think through. At this point it's a quick check to see if it would be possible. Later on there's code to consider all the variables, expiry time, stock levels, min number of doses, average usage of size, but that runs at a part when the hourglass can kick in and the user can wait. But don't want to go down this route if the end result is going to be no available stock combination.

I have used Target dose - Largest size = Remainder then try to assign to the remainder which works well where there's only 4 size options but became difficult to follow for more sizes with the additional rules I'd use to minimise the calculation burden.

As we're only trying to answer is it possible in any combination it's hard to know if you'll get to the True answer by starting with the smaller variable or the larger variable but I take your point Doc Larger first is probably better.
 
The code is a loop so it doesn't matter how many sizes there are. For example, 250, 125, 50, 25. If you want a dose of 275, you end up with 250 + 25. It doesn't matter that 2(125), and 1(25) also satisfy or if 11(25) works. Starting from the large size, you always get the most efficient breakdown. The middle sizes are tried and discarded. The complications arise if you want to limit the number of pills and the available sizes can't satisfy the requirement. Or, you don't have inventory in the sizes that will work. The looping also is very complicated if the sizes are not multiples. For example if 37.5 and 25 were the only options a final solution could not be found without backing out and trying again which gets really complicated.

You can get to "True" from either direction assuming the available sizes provide a solution, but do you want to dispense 11 pills or 2 is the question.
 
Last edited:
AFAIK all the proposed solutions are pretty infeasible if the max amount of pills was unconstrained. You have varying amount of doses and a varying amount of mixes. 1 pill solution, 2 pill solution, 3 pill solution, .... N pill solution. You cannot "loop" all solutions because you do not know how many potential loops. However, since you specify that a maximum 3 pill mix is only allowed this solution can be easily done as EBS17 proposes.

The unconstrained solution is much harder to do, but the algorithms to do this are very well known. Normally this is done normally with a spanning tree. This is often the first step in doing other optimization problems such as a Cutting Stock problem. You have to define all the feasible patterns. This attachedf PDF article does a good job of defining the spanning tree to get feasible patterns. I use a version of this algorithm. If you start adding values to the preferred choices this problem becomes a Knapsack problem.

Cutting Stock Discussion:
KnapSack and Bin Packing Discussion and algorithms

In my solution I have a table of drugs and related pill doses. You can add as many drugs as you want and there potential pill sizes.

tblDrugs tblDrugs

drugIDdrugName
1​
Drug A
2​
Drug B
tblDrugDoses tblDrugDoses

drugDoseIDDoseUnitsdrugID_FK
1​
55​
mg
1​
2​
75​
mg
1​
3​
80​
mg
1​
4​
90​
mg
1​
5​
100​
mg
1​
6​
120​
mg
1​
7​
50​
mg
2​
8​
40​
mg
2​
9​
30​
mg
2​
10​
20​
mg
2​
Drug A is your example, Drug B is the example in the PDF.

Using Drug B and 130 mg total dose. You have pill sizes of 50, 40, 30, 20 You create a spanning tree as follows to get all feasible solutions.

Tree.png


Basically you work from left of the tree to right. Algorithm is explained in detail.
The table might make it easier to explain
Table.png




Create far left branch 1
1. Order pill sizes from large to small
2. Start with 130 and figure out how many of level 1 (50 mg) can divide into it. That is 2. You have a remainder of 30.
3. Next go to level 2 (40 mg) and see how many of 40 can go into the remainder. That is zero.
4.Next go to level 3 (30) mg and see how many can go into the remainder (30) and that is 1.
5. Next go to level 4 (20) and see how many can go into the remainder (0) and that is 0.

Now you work from the bottom up to create the next branch (Column 2).
1. move up the first branch above the last level until you find a level with a quantity of more than 0.
2. There is a 1 in level 3 (30)
3. Decrement that by 1 and recalculate the residual.
4. Work back down.
5. See how many level 4 (20) can go into the remainder (30). And that is 1.

Keep doing this until there is no quantiies above the last level.

Here is the class module to do it.

Code:
Option Compare Database
Option Explicit

'To match example make the rows and columns 1 based
Private m_DrugID As Long
Private m_Column_J As Long
Private m_Level_I As Long

Private m_Doses() As Long
Private m_TotalDosage As Long
Private m_MaxLevel As Integer
Private m_LastQuantities() As Long
Private m_IncludeUnfilled As Boolean
Private m_IncludeZeroQuantity As Boolean
Private M_RS As DAO.Recordset

Public Sub Initialize(drugID As Long, totalDosage As Long, Optional IncludeZeroQuantity As Boolean = False, Optional IncludeUnfilled As Boolean)
  Dim strSql As String
  Dim rs As DAO.Recordset
  Dim I As Long
  Dim startTime As Long
  startTime = Timer
  'IncludeUnfilled used to log patterns where there is still leftover/residual
  'In this problem you would not include these, but in general pattern building you would
  'IncludeZeroQuantity used if you want to show pill sizes that are not used
  m_DrugID = drugID
  m_TotalDosage = totalDosage
  m_IncludeUnfilled = IncludeUnfilled
  m_IncludeZeroQuantity = IncludeZeroQuantity
  m_Level_I = 1
  strSql = "Select Dose from tblDrugDoses where DrugID_FK = " & drugID & " ORDER BY Dose desc"
  Set rs = CurrentDb.OpenRecordset(strSql)
  rs.MoveLast
  rs.MoveFirst
  ReDim m_Doses(1 To rs.RecordCount)
  ReDim m_LastQuantities(1 To rs.RecordCount)
  I = 1
  Do While Not rs.EOF
    m_Doses(I) = rs!Dose
    rs.MoveNext
    I = I + 1
  Loop
  m_MaxLevel = I - 1

UpdateNewColumn 0
InitializeRecordset
FillTable
Debug.Print "Time to complete: " & Timer - startTime '78 sec at 1500 drug a
End Sub
Private Sub InitializeRecordset()
  Set M_RS = CurrentDb.OpenRecordset("Select * from tblFeasibleDoses where true = false", dbOpenDynaset)
End Sub
Public Sub UpdateNewColumn(currentRow As Integer)
  Dim TheDose As Long
  Dim TheDoseQuantity As Long
  Dim TheResidual As Long
  Dim strSql As String
  Dim I As Integer

  'Update the new column of the array of quantities, but only log when residual is 0
  TheResidual = m_TotalDosage
  For I = 1 To UBound(m_Doses)
    TheDose = m_Doses(I)
    If I <= currentRow Then
      TheDoseQuantity = m_LastQuantities(I)
    Else
      TheDoseQuantity = SmallestInt(TheResidual, TheDose)
      m_LastQuantities(I) = TheDoseQuantity
    End If
    TheResidual = TheResidual - TheDose * TheDoseQuantity
    Next I
  'Debug.Print TheResidual
  If m_IncludeUnfilled = True Or TheResidual = 0 Then LogFeasible
End Sub

Public Sub LogFeasible()
  Dim strSql As String
  Dim I As Long
  Dim TheDose As Long
  Dim TheDoseQuantity As Long
  Dim TheResidual As Long
  'for simplicity add the new records, but the insert and update could be all done at once
  m_Column_J = m_Column_J + 1

  TheResidual = m_TotalDosage
  For I = 1 To UBound(m_Doses)
    TheDose = m_Doses(I)
    TheDoseQuantity = m_LastQuantities(I)
    m_LastQuantities(I) = TheDoseQuantity
    TheResidual = TheResidual - TheDose * TheDoseQuantity
    If M_RS Is Nothing Then InitializeRecordset
    If TheDoseQuantity <> 0 Or m_IncludeZeroQuantity Then
        M_RS.AddNew
          M_RS!totalDosage = m_TotalDosage
          M_RS!DrugID_FK = m_DrugID
          M_RS!DoseValue = m_Doses(I)
          M_RS!DoseQuantity = TheDoseQuantity
          M_RS!solutioncolumn = m_Column_J
          M_RS!Solutionrow = I
          M_RS!Residual = TheResidual
        M_RS.Update
    End If
   Next I
End Sub

Public Sub FillTable()
  Dim I As Integer
  Dim SomethingFound As Boolean
  Do
   ' Debug.Print LastQuantitiesToString
    For I = UBound(m_LastQuantities) - 1 To 1 Step -1
     ' Debug.Print m_LastQuantities(I)
      SomethingFound = False
      If m_LastQuantities(I) > 0 Then
        m_LastQuantities(I) = m_LastQuantities(I) - 1
        m_LastQuantities(UBound(m_LastQuantities)) = 0
        UpdateNewColumn I
        SomethingFound = True
        Exit For
      End If
    Next I
   
  Loop Until SomethingFound = False
End Sub


Private Function SmallestInt(Residual As Long, TheDose As Long)
  SmallestInt = Residual \ TheDose
End Function

'--------------------------------------------------- To String-----------------------------------------------------------
Public Function DosesToString() As String
  Dim strout As String
  Dim I As Long
  For I = 1 To UBound(m_Doses)
    If strout = "" Then
      strout = m_Doses(I)
    Else
      strout = strout & ", " & m_Doses(I)
    End If
  Next I
  DosesToString = strout
End Function
Public Function LastQuantitiesToString() As String
  Dim strout As String
  Dim I As Long
  For I = 1 To UBound(m_LastQuantities)
    If strout = "" Then
      strout = m_LastQuantities(I)
    Else
      strout = strout & ", " & m_LastQuantities(I)
    End If
  Next I
   LastQuantitiesToString = strout
End Function
Public Function ToString() As String
   ToString = "Required Dosage: " & m_TotalDosage
   ToString = "Levels: " & m_MaxLevel
   ToString = ToString & vbCrLf & "Doses: " & DosesToString
End Function

Here is the algorithm. Still very efficient. Will give you all feasible solutions for any pill combination and Total dosage in fraction of seconds.

flow.png
 

Attachments

Last edited:
Part 2
So using your data for Drug A and 250. All possible patterns are saved to the table.

This is the 1st two solution for the 29 potential patterns. (Cannot post all here do to size)


DrugID_FKTotalDosageSolutionColumnSolutionRowDoseValueDoseQuantityResidual
1​
250​
1​
1​
120​
2​
10​
1​
250​
1​
2​
100​
0​
10​
1​
250​
1​
3​
90​
0​
10​
1​
250​
1​
4​
80​
0​
10​
1​
250​
1​
5​
75​
0​
10​
1​
250​
1​
6​
55​
0​
10​
1​
250​
2​
1​
120​
1​
130​
1​
250​
2​
2​
100​
1​
30​
1​
250​
2​
3​
90​
0​
30​
1​
250​
2​
4​
80​
0​
30​
1​
250​
2​
5​
75​
0​
30​
1​
250​
2​
6​
55​
0​
30​
Solution 1 is
2 x 120 with a waste of 10
Solution 2 is
1 x 120
1 x 100
with a waste of 30.


Not very easy to read, but you could format into a report.
Pattern 1 is 2 x 120 with a waste of 10
Pattern 2 is 1 x 100, 1 x 120 with a waste of 30
etc

Patterns.png


In my solution I did all feasible patterns, but your problem just needs to return those patterns with 0 waste.
 
Last edited:
The way this should work is you need to enter your drugs and the possible pill sizes.
In the main form select your drug, it then displays the possible pill sizes. Type in the dosage. Then hit the button to create a solution.
The subform shows all possible solutions.
working.png
 
Last edited:
MajP that's a stunning solution, runs supper fast and I can build the start point on stock sizes available so Wow.

The fact that the solution gives a Dosage Pattern a few rules can select the best pattern for our purpose to use later on in the code. This option will speed up the later processes.

Going to take some time to work in but Absolutly Amazing Thank you.
 
Impressive analysis MajP. The OP said a max of 3 combinations. Which should trim the tree. Also, the OP would need to confirm, that not all drugs are available/produced in all dosages?
 
MajP that's a stunning solution, runs supper fast and I can build the start point on stock sizes available so Wow.
I have a degree that included a lot of optimization and mathematical modeling. I may not know how to do it anymore but do know where to go look. This one is actually relatively simple to code. But these types of problems can get surprisingly out of control fast. Trying to loop through all the possibilities may not even be possible. Something more complicated is a Dijkstra or Floyd Warshal, but I have demoed a few optimization models using Access. Normally these are heuristics because coding a guaranteed solution tends to get real difficult.

This spanning tree problem is not really an optimization, but it is commonly used in setting up other problems.

Also, the OP would need to confirm, that not all drugs are available/produced in all dosages?
That is not an issue. Each drug has child records showing what are the available dosages.

The OP said a max of 3 combinations. Which should trim the tree.
Since the OP stated a Max of 3 the solution can be done as EBS17 points out. I could have modified the code to let the user define the max pill size. The algorithm would be the same, but it would only log these solutions and kick out of the algorithm early.
Pills.png
 
Last edited:
Got to say it is an impressive solution, @MajP - my little exposure to queueing theory was enough to get me out of trouble more often than in trouble, but I could never avoid trouble completely since I'm originally a chemist, not a mathematician. My entrance to computer work was a back-door sort of thing necessitated by family circumstances. Despite needing some advanced math for my doctorate, it wasn't that particular branch of math. I've sort of picked up some of the practical tricks over a few years, but it helps if you actually studied the subject in depth.
 
Thank you MajP this has been a huge help and stopped me spending a lot of time on what I fear would have been a sub standard solution.
 
FYI, I did not originally see that you were limiting to three pills. If that is the case doing what @ebs17 suggests is probably the easy way to go. I incorporated a version here. The query solution is simple and fast and does not require code. It gets more tricky if you remove the three pill constraint. However if some dosages have different max amount of individual dosages you could make a 3,4,5...N individual queries.

The query is slightly modified to
Code:
SELECT A.dose                               AS Dose1,
       B.dose                               AS Dose2,
       C.dose                               AS Dose3,
       [a].[dose] + [b].[dose] + [c].[dose] AS TotalDose,
       C.drugid_fk
FROM   (qrydrugdoseswith0 AS A
        INNER JOIN qrydrugdoseswith0 AS B
                ON A.drugid_fk = B.drugid_fk)
       INNER JOIN qrydrugdoseswith0 AS C
               ON B.drugid_fk = C.drugid_fk
WHERE
( ( ( B.dose ) <= [a].[dose] )
  AND ( ( C.dose ) <= [b].[dose] )
  AND ( ( [a].[dose] + [b].[dose] + [c].[dose] ) =
            [forms] ! [frmmakepatternsquery] ! [txtrequired] )
  AND ( ( C.drugid_fk ) = [forms] ! [frmmakepatternsquery] ! [cmbodrug] ) )
ORDER  BY A.dose,
          B.dose,
          C.dose;

If this was a more generic problem with an unknown amount of partitions (assume something beside pill dosage like cutting a roll of steel) then that would be limiting. Assume it was something up to 10 partitions and 5 choices. Writing the query would be a pain, and this could blow up quickly because that cartesian returns 9 million records before getting filtered.
 

Attachments

Nice (for me) that my suggestion was taken up after all.
Writing the query would be a pain, and this could blow up quickly because that cartesian returns 9 million records before getting filtered.
A few comments on this:
- Before execution, SQL statements are just some text. Text can be generated and composed using VBA. My longest SQL statement composed by VBA was about 1200 characters.
- Large amounts of data due to the duplication of variants are of course a problem. This will push your limits, also in terms of performance. But large amounts of data are also a challenge for a developer - anyone without great skills can move 537 records back and forth.
The most time consuming will be the execution of the CROSS JOIN itself. The result could be written to a temporary table and the fields indexed there in order to be able to filter better.
- The overall calculation for a specific case would be carried out once (not live every time from the beginning) and the combinations found would be fixed in a table in order to look up there in the practical work in a significantly smaller amount.
 
Last edited:

Users who are viewing this thread

Back
Top Bottom