SebHallin
SebHallin

Reputation: 891

PHP - Difficult math calculation

Project description

The project calculates how much food a horse should have, this is based on a huge number of variables. The user enters information about each horse and preferred feeds. Each feed has multiple types of vitamins and nutrients.

1 horse uses multiple types of feeds.

What we have

We have the min and max values for each nutrient that the specific horse need.

We have the content for one or more different types of feed, this could be around 20-30 different nutrients and vitamins per feed. We also have the price and want to use this to save money.

(The calculation is only for one horse at the time)

Example

We use A, B and C to represent the nutrients.

Horse needs: (A,B,C) MIN(30,7,9) MAX(35,9,17)

Feed 1 contains: (A,B,C) VALUES(16,2,3)
Feed 2 contains: (A,B,C) VALUES(0,4,9)

A working solution would be 2*Feed1 and 1*Feed2.

Problem

I want the system to calculate the perfect balance based on the min/max values for each nutrient, and still keep the price as low as possible.

Working solution

If I first calculate the highest possible amount for each feed, it will be possible to randomize until it seems to work. And then the user will be able to change the amounts if it isn't perfect.

<?php
function randomizerLoop(){
    foreach($feeds as $feed){
        $max['A'] = floor($horse_max['A']/$feed['A']);
        $max['B'] = floor($horse_max['B']/$feed['B']);
        $max['C'] = floor($horse_max['C']/$feed['C']);
        $maxRand = MIN($max['A'], $max['B'], $max['C']);

        $amounts[$feed['id']] = rand(0, $maxRand);
    }

    return $amounts;
}
?>

This code would keep trying until it get a working balance, instead of using some cool calculation to find the balance on the first try.

I just need an idea how to solve it without rand().

More information

Each user will be able to add endless number of horses, but will only be able to calculate for one horse at the time (for now).

It will also be possible to add endless number of feeds (defined by the user), and each feed could have 20-30 variables. Depending on the solution, this would probably require a limit of feeds for each auto-calculation.

There would be a lot combinations if we use 20 different feeds, 20-30 variables for each feed, and also when we define amount in int instead of just booleans.

Upvotes: 14

Views: 661

Answers (3)

Golden_flash
Golden_flash

Reputation: 492

This is a linear programming problem.

     MIN     MAX
A    30      35
B    7       9
C    9       17

          A    B    C
Feed1     16   2    3
Feed2     0    4    9

Let the food contain x number of feed1 and y number of feed2. According to your question:

30<16*x+0*y<35
=> 30<16x<35

use ceil() to divide 30 by 16, that will give u x (which is 2). After that you have 3<4y<5, similarly use ceil() and you will get y=1.

Now thinking in terms of you project,

For large number of feeds it will be difficult to calculate so many equations.

We should use matrices for simplification.The martix for the equations given above will be :-

  A   *  B   =   C
|16 0| | x |   | 30 |
|2  4| | y | = |  7 |
|3  9|         |  9 |

Now use Lapack::pseudoInverse() to compute the inverse of matrix A and multiply it with C. Then simply equate the value of x and y in matrix B to the answer and use ceil().

Upvotes: 5

user2480047
user2480047

Reputation:

I would focus on the MIN values in order to minimise the cost. In any case, the whole point of the suggested approach is meeting certain targets, which might be as variable as required (for example: forcing nutrients E & G to get, at least, the half value between MIN & MAX).

The basic structure is formed by three loops: a main one going through all the nutrients; and two internal ones trying all the possible combinations among feeds.

NUTRIENT A
Trying feed1 until reaching minimum (because feed2 is zero). 
Best so far: 2*feed1=32A.

NUTRIENT B
Starting from 2*feed1=4B, +feed2 reaches minimum.
Best so far: 2*feed1+feed2=8B.

NUTRIENT C
Starting from 2*feed1+feed2=15C which is fine already.
Best so far: 2*feed1+feed2=15C.

A more advanced version of this approach would be going back to check (e.g., in cases where the target is reached since a first moment, like what happens with nutrient C) whether a better alternative would be possible. In any case, the complexity of such an implementation and the number of combinations would be notably higher.

I think that this is a fairly adaptable and accurate approach. Implementing the algorithm for the basic version is not too difficult (but neither too straightforward; that's why I haven't written it here) and should deliver a reasonably good accuracy and speed. After testing it and getting an idea about its performance, you might start thinking about improving it further (e.g., via going back to re-analyse certain case or accounting for non-MIN targets).

Upvotes: 3

kainaw
kainaw

Reputation: 4334

What you want to do is try every combination of every feed. Lets assume that there are 5 types of feeds. You already know how to calculate the maximum of each feed type. So, I assume you have something like this:

$feeds_cost = array of costs for each feed
$feeds_ingredient_A = array of how much ingredient A each feed has
$feeds_ingredient_B = array of how much ingredient B each feed has
$feeds_ingredient_C = array of how much ingredient C each feed has
$feeds_max = array of maximum quantity of each feed allowed

Now, for the 5 types, you want to try quantities {0,0,0,0,0}, {0,0,0,0,1}, {0,0,0,0,2} ... {$feeds_max[0], $feeds_max[1], $feeds_max[2], $feeds_max[3], $feeds_max[4]}. For each quantity set, you need to: 1. Ensure that the quantity meets the minimum requirement. 2. If it does meet the minimum requirement, calculate the cost.

So, at this point, you have the cost for every quantity that meets the minimum requirement, but doesn't surpass the maximum requirement. You don't need to store all of them. Maintain two variables: $best_quantities (an array of counts of each feed) and $best_cost. If a quantity has a cheaper cost while meeting the requirements, you replace $best_quantities and $best_cost.

Everything is trivial except stepping through all the quantities. That isn't really very hard. You maintain the index you are incrementing. Initially, it is zero, but it goes up to the highest feed index (4 if you have 5 feeds). The increment function is:

function increment_counts($feeds_max, $quantities, $index)
{
    if($index>sizeof($quantities)) return null;
    $quantities[$index]++;
    if($quantities[$index] > $feeds_max[$index])
    {
        $quantities[$index]=0;
        return increment_counts($feeds_max, $quantities, $index+1);
    }
    return $quantities;
}

Assuming that I typed that correctly off the top of my head, it will count through the quantities. You keep calling it by incrementing index 0 and replacing your current quantity with the return value. When it returns null, you are done.

I am certain that I made at least one oversight, but this is how I would tackle this problem.

Upvotes: 4

Related Questions