Основы оптимизации
Условная оптимизация
Задачи условной оптимизации состоят в поиске максимума или минимума заданной функции при наличии определенных условий, накладывающих ряд ограничений на возможные решения задачи.
В этом разделе мы рассмотрим задачу условной оптимизации по подбору оптимального рациона питания.
1. Создание условной оптимизационной задачи
Для решения данной задачи мы используем оптимизационную библиотеку JuMP и линейный решатель GLPK. Нам также понадобятся библиотеки XLSX и DataFrames для работы с табличными данными и библиотека Plots для визуализации результатов. Для их использования необходимо выполнить команду ниже:
using JuMP, GLPK, XLSX, DataFrames, Plots;
Загрузите таблицу с данными, выполнив команду ниже:
Nutrition = DataFrame(XLSX.readtable("nutrition.xlsx", "Sheet1"))
Таблица содержит информацию о содержании питательных веществ в двадцати различных продуктах, а также их условную цену.
✏️Задание 1
Создайте оптимизационную задачу, применив к ней линейный решатель GLPK, и сохраните ее в переменной Opt_ration.
Решение
Opt_ration = Model(GLPK.Optimizer)
✏️Задание 2
Используя команду @variable(), создайте переменную Portions, определяющую количество порций того или иного продукта, которое будет рассчитано исходя из поставленных нами условий подбора рациона. Используйте Portions[], чтобы обозначить необходимое количество элементов переменной Portions для размещения всех видов продуктов таблицы Nutrition.
Для текущей задачи через запятую также необходимо указать, что значения переменных не могут быть меньше нуля (lower_bound = 0).
Решение
@variable(Opt_ration, Portions[1:20], lower_bound = 0)
Данная переменная будет содержать количество порций того или иного продукта, полученное нами после выполнения условий подбора рациона.
✏️Задание 3
В данном упражнении мы минимизируем затраты на наш рацион. Поэтому целевая функция нашей оптимизационной задачи будет основана на стоимости продуктов питания.
Перемножьте столбец Nutrition."Price" на переменную Portions и сохраните результат в переменной Price.
Подсказка
Используйте .* для перемножения векторов.
Решение
Price = Nutrition."Price" .* Portions
Теперь переменная Price будет содержать стоимость полученного нами в дальнейшем количества порций того или иного продукта питания.
✏️Задание 4
Суммируйте все значения переменной Price, чтобы получить стоимость всего рациона, и сохраните результат в переменной TotalPrice.
Решение
TotalPrice = sum(Price)
✏️Задание 5
Создайте целевую функцию для нашей оптимизационной задачи Opt_ration с помощью команды @objective(). Помните, что нам необходимо найти возможный минимум затрат на все продукты питания, из которых мы можем составить наш рацион.
Подсказка
Используйте Min и TotalPrice через запятую как второй и третий аргумент в команде @objective().
Решение
@objective(Opt_ration, Min, TotalPrice)
✏️Задание 6
Проверьте работу целевой функции на данном этапе. Для этого решите оптимизационную задачу и визуализируйте полученный результат.
Решите оптимизационную задачу и просмотрите ее параметры с помощью команд optimize!() и display().
Решение
optimize!(Opt_ration)
display(Opt_ration)
✏️Задание 7
Получите оптимизированные значения порций из переменной Portions. Для этого используйте команду value.(). Обратите внимание, что так как оптимизационная переменная Portions является вектором, нам необходимо добавить . в команде перед скобками. Сохраните результат в новой переменной Op_portions.
Решение
Op_portions = value.(Portions)
Совпадают ли результаты с вашими ожиданиями? Попробуйте объяснить, почему оптимизированные значения равны нулю.
✏️Задание 8
Для визуализации результатов мы будем использовать библиотеку Plots. Для этого мы присоединим вектор с оптимизированными значениями порций Op_portions к изначальной таблице Nutrition, переведем в тип DataFrame и сохраним в новой переменной Price_port. Затем передадим нашу новую переменную в команду plot, укажем имена столбцов, которые хотим визуализировать, и поставим тип графика как bar. Столбец Op_portions, значения которого мы хотим визуализировать, при добавлении к таблице по умолчанию получает имя x1. Выполните команды ниже:
Price_port = DataFrame([Nutrition Op_portions])
plot(Price_port.Name, Price_port.x1, seriestype="bar")
2. Условие калорийности рациона
Теперь создадим условия оптимизации нашего рациона. Мы оптимизируем рацион по количеству калорий, белков, углеводов и жиров, которые он должен содержать.
Для начала установим границы калорийности нашего рациона.
✏️Задание 9
Перемножьте столбец Nutrition."Calories" на переменную Portions и сохраните результат в переменной Calories.
Решение
Calories = Nutrition."Calories" .* Portions
✏️Задание 10
Рассчитайте сумму всех элементов вектора Calories и сохраните ее в переменной TotalCals.
Решение
TotalCals = sum(Calories)
✏️Задание 11
Создайте условие для оптимизационной задачи Opt_ration с помощью команды @constraint(). В команде через запятую укажите имя условия как C1 и границы переменной TotalCals между 2000 и 2500 калорий.
Решение
@constraint(Opt_ration, C1, 2000 <= TotalCals <= 2500)
✏️Задание 12
Решите оптимизационную задачу с заданным условием и просмотрите ее параметры с помощью команд optimize!() и display().
Решение
optimize!(Opt_ration)
display(Opt_ration)
✏️Задание 13
Получите новые оптимизированные значения порций из переменной Portions. Для этого используйте команду value.(). Обратите внимание, что так как оптимизационная переменная Portions является вектором, нам необходимо добавить . в команде перед скобками. Сохраните результат в переменной Op_portions.
Затем визуализируйте новые результаты. Для этого присоедините вектор с оптимизированными значениями порций Op_portions к изначальной таблице Nutrition, переведите в тип DataFrame и сохраните в новой переменной Cals_port. Затем передайте нашу новую переменную в команду plot по аналогии с Заданием 8.
Решение
Op_portions = value.(Portions)
Cals_port = DataFrame([Nutrition Op_portions])
plot(Cals_port.Name, Cals_port.x1, seriestype="bar")
Теперь результат отображает выполнение условия калорийности при минимальной стоимости рациона.
3. Условие наличия минимального количества белка
Теперь мы зададим дополнительное условие по наличию минимального количества белка в нашем рационе.
✏️Задание 14
Перемножьте столбец Nutrition."Protein (g)" на переменную Portions и сохраните результат в переменной Protein.
Решение
Protein = Nutrition."Protein (g)" .* Portions
✏️Задание 15
Рассчитайте сумму всех элементов вектора Protein и сохраните в переменной TotalProt.
Решение
TotalProt = sum(Protein)
✏️Задание 16
Создадим условие, чтобы наш рацион содержал как минимум 100 граммов белка.
Создайте условие для оптимизационной задачи Opt_ration с помощью команды @constraint(). В команде через запятую укажите имя условия как C2 и значение переменной TotalProt больше или равно (>=) 100.
Решение
@constraint(Opt_ration, C2, TotalProt >= 100)
✏️Задание 17
Решите оптимизационную задачу с заданным условием и просмотрите ее параметры с помощью команд optimize!() и display().
Решение
optimize!(Opt_ration)
display(Opt_ration)
✏️Задание 18
Получите новые оптимизированные значения порций из переменной Portions. Для этого используйте команду value.(). Обратите внимание, что так как оптимизационная переменная Portions является вектором, нам необходимо добавить . в команде перед скобками. Сохраните результат в переменной Op_portions.
Затем визуализируйте новые результаты. Для этого присоедините вектор с оптимизированными значениями порций Op_portions к изначальной таблице Nutrition, переведите в тип DataFrame и сохраните в новой переменной Prot_port. Затем передайте нашу новую переменную в команду plot.
Решение
Op_portions = value.(Portions)
Prot_port = DataFrame([Nutrition Op_portions])
plot(Prot_port.Name, Prot_port.x1, seriestype="bar")
Теперь результат отображает выполнение условий калорийности и наличия минимального количества белка при минимальной стоимости рациона.
4. Условие наличия минимального количества жиров
Зададим дополнительное условие по наличию минимального количества жиров в нашем рационе.
✏️Задание 19
Перемножьте столбец Nutrition."Fat (g)" на переменную Portions и сохраните результат в переменной Fats.
Решение
Fats = Nutrition."Fat (g)".* Portions
✏️Задание 20
Рассчитайте сумму всех элементов вектора Fats и сохраните в переменной TotalFats.
Решение
TotalFats = sum(Fats)
✏️Задание 21
Создадим условие, чтобы наш рацион содержал как минимум 70 граммов жиров.
Создайте условие для оптимизационной задачи Opt_ration с помощью команды @constraint(). В команде через запятую укажите имя условия как C3 и значение переменной TotalFats больше или равно (>=) 70.
Решение
@constraint(Opt_ration, C3, TotalFats >= 70)
✏️Задание 22
Решите оптимизационную задачу с заданным условием и просмотрите ее параметры с помощью команд optimize!() и display().
Решение
optimize!(Opt_ration)
display(Opt_ration)
✏️Задание 23
Получите новые оптимизированные значения порций из переменной Portions. Для этого используйте команду value.(). Обратите внимание, что так как оптимизационная переменная Portions является вектором, нам необходимо добавить . в команде перед скобками. Сохраните результат в переменной Op_portions.
Затем визуализируйте новые результаты. Для этого присоедините вектор с оптимизированными значениями порций Op_portions к изначальной таблице Nutrition, переведите в тип DataFrame и сохраните в новой переменной Fats_port. Затем передайте нашу новую переменную в команду plot.
Решение
Op_portions = value.(Portions)
Fats_port = DataFrame([Nutrition Op_portions])
plot(Fats_port.Name, Fats_port.x1, seriestype="bar")
5. Условие наличия минимального количества углеводов
Зададим последнее дополнительное условие по наличию минимального количества углеводов в нашем рационе.
✏️Задание 24
Перемножьте столбец Nutrition."Carbohydrate (g)" на переменную Portions и сохраните результат в переменной Carbs.
Решение
Carbs = Nutrition."Carbohydrate (g)" .* Portions
✏️Задание 25
Рассчитайте сумму всех элементов вектора Carbs и сохраните в переменной TotalCarbs.
Решение
TotalCarbs = sum(Carbs)
✏️Задание 26
Создадим условие, чтобы наш рацион содержал как минимум 400 граммов углеводов.
Создайте условие для оптимизационной задачи Opt_ration с помощью команды @constraint(). В команде через запятую укажите имя условия как C4 и значение переменной TotalCarbs больше или равно (>=) 400.
Решение
@constraint(Opt_ration, C4, TotalCarbs >= 400)
✏️Задание 27
Решите оптимизационную задачу с заданным условием и просмотрите ее параметры с помощью команд optimize!() и display().
Решение
optimize!(Opt_ration)
display(Opt_ration)
6. Итоговые результаты
Получите новые оптимизированные значения порций из переменной Portions. Для этого используйте команду value.(). Обратите внимание, что так как оптимизационная переменная Portions является вектором, нам необходимо добавить . в команде перед скобками. Сохраните результат в переменной Op_portions.
Визуализируйте итоговый результат оптимизационной задачи. Для этого присоедините вектор с оптимизированными значениями порций Op_portions к изначальной таблице Nutrition, переведите в тип DataFrame и сохраните в новой переменной Total_port. Затем передайте нашу новую переменную в команду plot.
Вы можете вывести информацию о решениях и убедиться, что они удовлетворяют заданным вами условиям с помощью команды solution_summary().
Решение
Op_portions = value.(Portions)
Total_port = DataFrame([Nutrition Op_portions])
plot(Total_port.Name, Total_port.x1, seriestype="bar")
solution_summary(Opt_ration)