Пусть у нас есть картофель фри, котлета, хлеб, помидор, огурец и молочный коктейль. Сколько чего нужно съесть, чтобы получилось 30 гр. белка, 25 гр. жиров и 60 гр. углеводов? В прошлый раз я баловался и пытался решить это с помощью матриц, на этот раз - с помощью линейных уравнений и python библиотеки PuLP.

Немного про PuLP (Python Linear programming)

Пользоваться этим модулем очень просто.

Например, если нам нужно создать переменную 0 <= x <= 3, то синтаксис будет такой:

x = LpVariable("x", 0, 3)

Для переменной 0 <= y <= 1:

y = LpVariable("y", 0, 1)

Функция LpProblem() для создания задачи:

prob = LpProblem("Моя проблемка", LpMinimize)

Дальше можно комбинировать переменные, выражения и константы, добавляя их в задачу:

prob += x + y <= 2

Если вы добавите выражение (не ограничение), оно станет целью: (в оригинале If you add an expression (not a constraint), it will become the objective:)

prob += -4*x + y

Для решения можно использовать встроенный "решатель":

status = prob.solve()

Просмотр статуса у решения:

LpStatus[status]
> 'Optimal'

И можно после этого получить значение:

value(x)
> 2.0

Полная документация https://coin-or.github.io/pulp/.

Решение с помощью PuLP полный код

Вот такие у нас есть продукты с такими белками, жирами и углеводами ( БЖУ) на 100 гр. продукта:

Белки

Жиры

Углеводы

Картофель фри

3,4

15

41

Котлета

14,1

15,7

6,6

Хлеб

8,8

3,3

46,7

Помидор

1,1

0,2

3,8

Огурец

0,8

0,1

2,5

Молочный коктейль

6

5

45

Ставим:

pip install pulp

Полный код программы, можно вставить в файл и запустить:

import pulp as pl


data = {
    'Картошка фри': {'Б': 3.4, 'Ж': 15,  'У': 41},
    'Котлета': {'Б': 14.1, 'Ж': 15.7, 'У':      6.6},
    'Хлеб': {'Б': 8.8, 'Ж': 3.3, 'У':  46.7},
    'Помидор': {'Б': 1.1,  'Ж': 0.2, 'У': 3.8},
    'Молочный коктейль': {'Б': 6, 'Ж': 5, 'У': 45},
    'Огурец': {'Б': 0.8, 'Ж': 0.1, 'У': 2.5},
}


# Макронутриенты (БЖУ), которые нужно получить
needed = {'Б': 30, 'Ж': 25, 'У': 60}

# Минимум 10 гр. надо съесть, иначе может посчитать с нулями
# Можно задать для каждого продукта отдельно
MIN = 0.1
# Максимум 150 гр.
MAX = 1.5


prob = pl.LpProblem("The Nutrients Problem", pl.LpMinimize)
# Создаем переменные для каждого продукта
food = pl.LpVariable.dicts('Food', data, MIN, MAX)
for nutrient in list('БЖУ'):
    # Для бел., жиров и угл. создаем условия
    prob += pl.lpSum([data[k][nutrient] * food[k]
                      for k in data]) == needed[nutrient]

prob.writeLP("Nutrients.lp")
prob.solve()

# Нельзя съесть -минус 10 гр. огурцов. Значит решения нет, если есть хоть 1
# отрицательное значение
if any((v.varValue or 0) < 0 for v in prob.variables()) is False:
    for v in prob.variables():
        if not v.varValue:
            continue
        name = v.name.replace('Food_', '')
        weight = int(v.varValue*100)
        print(f'{name}: {weight} гр.')

В коде есть такая строка (она не обязательна):

prob.writeLP("Nutrients.lp")

Она создает файл Nutrients.pl, если заглянуть внутрь него, то можно увидеть все формулы, константы и условия, что очень удобно:

\* The_Nutrients_Problem *\
Minimize
OBJ: __dummy
Subject To
_C1: 3.4 Food_Картошка_фри + 14.1 Food_Котлета + 6 Food_Молочный_коктейль
 + 0.8 Food_Огурец + 1.1 Food_Помидор + 8.8 Food_Хлеб = 30
_C2: 15 Food_Картошка_фри + 15.7 Food_Котлета + 5 Food_Молочный_коктейль
 + 0.1 Food_Огурец + 0.2 Food_Помидор + 3.3 Food_Хлеб = 25
_C3: 41 Food_Картошка_фри + 6.6 Food_Котлета + 45 Food_Молочный_коктейль
 + 2.5 Food_Огурец + 3.8 Food_Помидор + 46.7 Food_Хлеб = 60
Bounds
 0.1 <= Food_Картошка_фри <= 1.5
 0.1 <= Food_Котлета <= 1.5
 0.1 <= Food_Молочный_коктейль <= 1.5
 0.1 <= Food_Огурец <= 1.5
 0.1 <= Food_Помидор <= 1.5
 0.1 <= Food_Хлеб <= 1.5
 __dummy = 0
End

Отрицательные значения

Есть байка, что древние римляне ходили с гусиным перышком на пир, чтобы в нужный момент пощекотать себе горло, очистить желудок и наслаждаться пиршеством дальше. Но даже если бы Миа была бы древней римлянкой, и ходила с пером, то это бы ей не помогло, когда программа выдаст съесть - минус 100 грамм картошки фри. Поэтому я отбросил в коде такие вариации.

Код на гитхаб

Итого

Если бы Миа пришла в ресторан со своими весами, а не с запрещенными веществами, и с ясной целью получить 30 гр. белка, 25 гр. жиров и 60 гр. углеводов, то она должна была бы съесть:

  • Картошка фри: 10 гр.

  • Котлета: 128 гр.

  • Молочный коктейль: 10 гр. (После Винсента не стоит его пить)

  • Огурец: 150 гр.

  • Помидор: 150 гр.

  • Хлеб: 71 гр.

Комментарии (15)


  1. Markscheider
    20.09.2021 15:43
    +1

    Добавляйте в качестве одного из параметров "количество пищевых волокон", лепите приложение и выкатывайте в прод.

    Калькулятор будет полезен для тех, кто следит за рационом "вполглаза" и не хочет ставить более функциональный комбайн типа FatSecret.

    А в платной версии можно добавить такой же параметрический расчет по разным витаминам, простым углеводам (сахарам) и пр. С добычей исходных данных по содержанию того или иного компонента в продукте/блюде, правда, придется повозиться.


    1. pcdesign Автор
      20.09.2021 15:50

      Спасибо. А я что-то мыслю категориями для дома и для семьи, и не задумывался о таком использовании ))

      Параметр, да, добавить не сложно, в базе usda есть эти данные, я про нее писал в прошлой статье. А вы учитываете этот параметр в своем рационе или хотели бы его учитывать? Почему именно количество пищевых волокон, а не витамин А, например?


  1. Markscheider
    20.09.2021 16:18

    А вы учитываете этот параметр в своем рационе или хотели бы его учитывать? Почему именно количество пищевых волокон, а не витамин А, например?

    Учитываю клетчатку. Почему не витамины? Я считаю, что:

    1) Если питаться разнообразно и полноценно (в соответствии с рекомендациями, ВОЗ, например) - все необходимые витамины приходят с едой. Исключение - витамин Д, но с ним все немного сложнее.

    2) Если я даже увижу, что в рационе, например, не хватает витамина А - я не буду покупать его в аптеке, т.к. твердо убежден, что поливитамины (как и другие лекарства) должен разначать врач. Лучше - по ДМС :):):)

    А с клетчаткой дело другое: передознуться ей сложно (хотя можно), а вот недостаток ее при нашей рафинированной еде случается часто. Плюсуем малоподвижный образ жизни и получаем, как минимум, систематические запоры. Далее со всеми остановками.

    Вот поэтому я хотел бы помимо БЖУ контролить еще и пищевые волокна. Это, программа минимум, ессн.

    Буду рад уточнениям и разъяснениям от профессиональных медиков, если в моем комментарии что-то не так.

    P.S. Ожидаемо промахнулся веткой - не привык к новому дизайну Хабра :)


    1. pcdesign Автор
      20.09.2021 16:26

      Благодарю за развернутый ответ. Да, логика понятна. Клетчатка нужна, особенно при большом потреблении белка. Я пока только БЖУ считаю. Но на счет клетчатки задумывался тоже. А какие нормы на пищевые волокна? Вот сколько рекомендуется, женщине средних лет с именем Миа, потреблять пищевых волокон в день?

      Все же у меня не калькулятор, а подгонялка. Программа пытается именно подогнать исходя из заданных значений.


      1. Markscheider
        20.09.2021 16:39

        ВОЗ в рекомендациях для населения говорит о 400 гр овощей в сутки (пруф). Правда, мне, почему-то кажется, что картошка-фри в данном случае к овощам относиться не будет :):)
        А именно по чистой клетчатке (ее, как вы понимаете, в разных фруктах/овощах по-разному содержится) советуют взрослым по 25-30 грамм в день (пруф).

        Но надо понимать, что клетчатка есть еще и в зерновых продуктах, бобовых и пр. И, к сожалению, ее содержание (наряду с БЖУ) указывают не на всех магазинных продуктах.

        P.S. У меня есть ощущение, что в бургере с белой булочкой из муки высшего сорта и картошке-фри (очищенной, а не не "деревенской" со шкуркой) этой клетчатки содержится ноль целых, хрен десятых...


        1. pcdesign Автор
          20.09.2021 17:28

          Понятно, спасибо.

          Подгонялка выдала для нее 150 гр. помидор и 150 гр. огурцов, так что в принципе за один прием пищи 3/4 нормы она получит.


          1. Markscheider
            20.09.2021 17:58

            Чуток еще добавлю: не про программирование, а про еду :). Из собственных наблюдений.

            400 грамм свежих фруктов и овощей в день поначалу кажутся какой-то огромной и жуткой цифрой. Однако съедать это количество вполне реально. Главное - не увлекаться монодиетой (один вид овощей/фруктов). Это и скучно, да и с точки зрения разнообразия нутриентов неверно.

            Ну и в один прием пищи все 400 грамм упихивать не стоит - будет тяжеловато в желудке за счет объема.

            К зиме будет посложнее, а сейчас, по осени, и вовсе ненапряжно: настрогал на обед салатик из помидора с огурцом и болгарским перцем, на полдник рубанул подмосковное яблочко, а перед ужином зашла соседка и принесла кабачков (о, это слово!) со своего огорода...


            1. pcdesign Автор
              20.09.2021 18:05

              Да, раньше все думали, что достаточно просто считать калории. Потом пришли к тому, что нужно считать БЖУ. Видимо следующим шагом будет контроль над нормативами по клетчатке.


            1. Dolios
              21.09.2021 09:52

              400 грамм свежих фруктов и овощей в день поначалу кажутся какой-то огромной и жуткой цифрой.

              Кому кажутся? Это 2 яблока или 1 салат из свежих овощей.


              1. pcdesign Автор
                21.09.2021 10:03

                Возможно, тому у кого нет кухонных весов или тот кто не знает сколько весит среднее яблоко.


                1. Markscheider
                  21.09.2021 11:03

                  Вот про весы - жизненно. Лет пять назад купил электронные и теперь не понимаю - как я до этого без них обходился. Причем речь не только про учет калорий - это вообще в быту вещь незаменимая. Даже ингредиенты для алкогольных коктейлей :) удобнее отмерять на весах, нежели морочиться с градуированными стаканчиками.
                  На фото - овощи c ≈300 гр чистого веса. К слову, салат из них получится довольно мощный, если по столовским меркам - так на две порции...

                  Фото


  1. VPryadchenko
    20.09.2021 17:46

    Если вы добавите выражение (не константу), оно станет целью: (в оригинале If you add an expression (not a constraint), it will become the objective:)

    constraint - это не константа, это - ограничение.


    1. pcdesign Автор
      20.09.2021 17:52

      Поправил, спасибо


  1. Stems
    20.09.2021 18:23

    NNLS вам для решения таких уравнений надо. Это типичная квадратичная задачка.


    1. pcdesign Автор
      20.09.2021 18:26

      Матрицы были, LP были, теперь NNLS. Любопытно, надо попробовать, уже с клетчаткой про которую упоминали в комментах выше.