jddingemanse / awtiCodeDev

Development of code for and by Arba Minch University Water Technology Institute
1 stars 2 forks source link

Optimization: objective and constraint programming #4

Open jddingemanse opened 1 year ago

jddingemanse commented 1 year ago

Creating example code and Jupyter Notebooks for working with objectives and constraints.

jddingemanse commented 1 year ago

Nimrod and I have developed a script in which we use python-ortools for solving a harvest scheduling problem. See under code the file SowScheduleConstraints.py. It calculates a crop schedule for 1 year (but, the year is connected; sowing can be done in month 12 and then in months 1 and 2 it can be on the land based on that sowing). At this moment, the script becomes very slow when for multiple crops, for certain months, no crops are allowed (cropOffSeason). We are still checking into this. Edit, see next comment: we included a maximum calculation time.

Short instructions for working with the current script:

Monthly settings

available_water_per_month = [1705540]*12 available_land = 1176

Crop settings

crops = ['x1','x2','x3','x4','x5','x6','x7','x8'] cropcycle = dict(zip(crops,[3,4,4,4,5,12,4,4])) waterconstraint = np.array([321,317,271,298,322,582,262,352]) profit = [74750,38711,93600,192805,90060,135919*6,62483,123744]

cropOffSeason = {'x4':[3,4,9]}

Settings for crops that take more than a year

noYrs = 1 yrsToProfit = [0,0,0,0,0,1,0,0]

Solver settings

maxTime = 10 # Time in seconds maximum for the solver. Increase this one if you want to give the solver more time to find the best solution.


You can make the following changes:
- The available water per month: `available_water_per_month` should be a list with a length equal to 12 (so, for every month a value). Currently it is set to a fixed value (1705540).
- Available land: set it with `available_land`
- crops: provide a list with x1 to xx. At the moment, maximum 9 crops, because only two-digit cropnames work. For example, for 3 crops: `crops = ['x1','x2','x3']`
- cropcycle: the number of months that each crop needs from sowing to yield. Change the `list` part of `cropcycle = dict(zip(crops,[3,4,4,4,5,12,4,4]))` - currently for 8 crops the cropcycles are respectively 3,4,4,5,12,4,4 months. With three crops and copcycles 3,5,4, set this to `cropcycle = dict(zip(crops,[3,5,4]))`
- relative water use: set this with an array of equal length to the crops through `waterconstraint`. The current `np.array([321,317,271,298,322,582,262,352])` implies that 321*x1+317*x2+271*x3+... each month should be lower than the available water per month.
- the profit each crop gives: set `profit` to a list with length equal to the number of crops. This implies for every ha of crop the profit per cropcycle. _The 135919*6 represents, for the cropcycle of 12 months, a yield every two months._
- If certain crops cannot grow during certain months: add them to the dictionary `cropOffSeason`. To work without this, set `cropOffSeason = {}` (empty dictionary). To say that crop x1 cannot grow during months 1,2,3 and crop x5 not during months 5,6,7, set `cropOffSeason= {'x1':[1,2,3],'x5':[3,4,5]}`. **NOTE: this part makes the code slow! Probably, your solver will be busy until the set `maxTime`.**
- The number of years your want your schedule to take: set this with `noYrs`. A yearly schedule is created, but if you select for example 5 years, the profit for the yearly cycle is multiplied with 5. This variable, in combination with the next setting (`yrsToProfit`) can be used to also include a multi-year crop (such as banana, or even trees like mango or avocado). For such a crop, the cropcycle should be set to 12 (months).
- The number of years until a crop gives yield: set this with the list `yrsToProfit`, per crop. Crops with cycles less than a year should be set to 0, but crops with cycles of one or more years should be set to 1 or higher.
- The maximum time for the solver: set this with `maxTime` (value in seconds). If the solver finds a best solution within this time, it will give that. But when the problem gets very complex (in this case, for example with extensive use of `cropOffSeason`), then it will give the best solution found within the given `maxTime`. According to the programmers of Google ortools this is based on 'luck', meaning (we think) that you can do sensitivity analysis of the found answer by running the solver multiple times.

This is a work in progress of course, so comments or questions are much appreciated.
jddingemanse commented 1 year ago

Update: based on a question and answers to ortools (see here) we learned that a maximum time can be set for the solver. The solver tries to find the best solution, but with a complex problem, this might take a lot of time; instead, with setting a maximum time, after that time the solver will give you the best solution so far.

Also, we have included a number of years (noYrs) variable, and a setting for crops that need at least 1 year between sowing and harvest (yrsToProfit; if a crop needs 1 year, set the value at the corresponding position to 1). The total profit is now calculated as profit * (noYrs - yrsToProfit).

samuel1998979 commented 1 year ago

Dear Jan;

This is great! I would take an insight into it. I guess we need to have an eye on the optimization equation itself. Some brainstorming is needed with it.

I was wandering to register with Github but that didn't work with me quickly and In rather came directly from your link. Sorry for the late reply.

Samuel