DmitrMakeev commited on
Commit
f4bce88
·
verified ·
1 Parent(s): 6add92c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +93 -82
app.py CHANGED
@@ -735,121 +735,132 @@ class NutrientCalculator:
735
  self.target_profile = BASE_PROFILE.copy()
736
  self.fertilizers = NUTRIENT_CONTENT_IN_FERTILIZERS
737
  self.total_ec = 0.0
 
 
 
 
738
 
739
  # Расчёт азота
740
  total_parts = NO3_RATIO + NH4_RATIO
741
  self.target_profile['N (NO3-)'] = TOTAL_NITROGEN * (NO3_RATIO / total_parts)
742
  self.target_profile['N (NH4+)'] = TOTAL_NITROGEN * (NH4_RATIO / total_parts)
743
 
744
- # Сохраняем исходный профиль азота
745
- self.initial_n_profile = {
746
- "NO3-": self.target_profile['N (NO3-)'],
747
- "NH4+": self.target_profile['N (NH4+)']
748
- }
749
-
750
- # Список всех удобрений
751
- self.fertilizer_list = list(self.fertilizers.keys())
752
-
753
  def calculate(self):
754
  try:
755
- # Инициализация фактического профиля и результата
756
  self.actual_profile = {k: 0.0 for k in self.target_profile}
757
  self.results = {}
758
-
759
- # Начинаем рекурсивный поиск
760
  if self._backtrack_search():
761
- print("Успешная комбинация найдена!")
762
- return self.results
763
  else:
764
- raise ValueError("Не удалось найти подходящую комбинацию удобрений.")
765
 
766
  except Exception as e:
767
  print(f"Ошибка при расчёте: {str(e)}")
768
  raise
769
 
770
- def _backtrack_search(self, fertilizer_index=0):
771
- """Рекурсивный поиск с возвратом"""
772
- # Проверяем, достигнуты ли целевые значения
773
- if self.validate_results():
774
- return True
775
-
776
- # Если перебор, откатываемся назад
777
- if self._is_overage():
778
  return False
779
 
780
- # Пробуем добавить удобрения по очереди
 
 
 
 
 
 
 
 
 
 
 
 
 
 
781
  for i in range(fertilizer_index, len(self.fertilizer_list)):
782
  fert_name = self.fertilizer_list[i]
783
-
784
- # Применяем удобрение
785
- self._apply_fertilizer(fert_name)
786
-
787
- print(f"Добавлено удобрение: {fert_name}")
788
-
789
- # Рекурсивно пробуем следующее удобрение
790
- if self._backtrack_search(i + 1):
791
  return True
792
-
793
- # Откатываемся назад (удаляем последнее удобрение)
794
- self._remove_fertilizer(fert_name)
795
- print(f"Откат: удалено удобрение {fert_name}")
 
 
 
 
796
 
797
  return False
798
 
799
- def _apply_fertilizer(self, fert_name):
800
- """Применение удобрения"""
801
  if fert_name not in self.results:
802
- self.results[fert_name] = {
803
- 'граммы': 0.0,
804
- 'миллиграммы': 0,
805
- 'вклад в EC': 0.0
806
- }
807
- for elem in self.fertilizers[fert_name]:
808
- self.results[fert_name][f'внесет {elem}'] = 0.0
809
-
810
- # Находим максимальный недостаток среди всех элементов удобрения
811
- max_required_ppm = 0
812
- for element, content in self.fertilizers[fert_name].items():
813
- required_ppm = self.target_profile[element] - self.actual_profile[element]
814
- if required_ppm > 0.1:
815
- max_required_ppm = max(max_required_ppm, required_ppm / content)
816
-
817
- # Добавляем удобрение в количестве, необходимом для покрытия максимального недостатка
818
- grams = max_required_ppm * 1000
819
- self.results[fert_name]['граммы'] += grams
820
- self.results[fert_name]['миллиграммы'] += int(grams * 1000)
821
-
822
  for element, content in self.fertilizers[fert_name].items():
823
- added_ppm = (grams * content * 1000) / self.volume
824
  self.results[fert_name][f'внесет {element}'] += added_ppm
825
  self.actual_profile[element] += added_ppm
 
 
 
 
826
 
827
- def _remove_fertilizer(self, fert_name):
828
- """Удаление удобрения"""
829
  if fert_name in self.results:
830
- grams = self.results[fert_name]['граммы']
 
 
 
831
  for element, content in self.fertilizers[fert_name].items():
832
- added_ppm = (grams * content * 1000) / self.volume
833
- self.actual_profile[element] -= added_ppm
834
- del self.results[fert_name]
835
-
836
- def _is_overage(self):
837
- """Проверка на перебор"""
838
- for element, target_value in self.target_profile.items():
839
- actual_value = self.actual_profile[element]
840
- if actual_value > target_value + 0.1: # Допустимая погрешность
841
- print(f"Перебор: {element} (цель: {target_value:.2f}, фактически: {actual_value:.2f})")
842
- return True
843
- return False
 
 
 
 
 
844
 
845
- def validate_results(self):
846
- """Проверка соответствия целевым значениям"""
847
- for element, target_value in self.target_profile.items():
848
- actual_value = self.actual_profile[element]
849
- if abs(actual_value - target_value) > 0.1: # Допустимая погрешность
850
- print(f"Несоответствие: {element} (цель: {target_value:.2f}, фактически: {actual_value:.2f})")
851
- return False
852
- return True
 
 
 
 
 
 
 
 
853
 
854
  def calculate_ec(self):
855
  return round(self.total_ec, 2)
 
735
  self.target_profile = BASE_PROFILE.copy()
736
  self.fertilizers = NUTRIENT_CONTENT_IN_FERTILIZERS
737
  self.total_ec = 0.0
738
+ self.best_solution = None
739
+ self.min_difference = float('inf')
740
+ self.max_recursion_depth = 1000 # Ограничение глубины рекурсии
741
+ self.current_depth = 0
742
 
743
  # Расчёт азота
744
  total_parts = NO3_RATIO + NH4_RATIO
745
  self.target_profile['N (NO3-)'] = TOTAL_NITROGEN * (NO3_RATIO / total_parts)
746
  self.target_profile['N (NH4+)'] = TOTAL_NITROGEN * (NH4_RATIO / total_parts)
747
 
 
 
 
 
 
 
 
 
 
748
  def calculate(self):
749
  try:
 
750
  self.actual_profile = {k: 0.0 for k in self.target_profile}
751
  self.results = {}
752
+ self.current_depth = 0
753
+
754
  if self._backtrack_search():
755
+ print("Оптимальная комбинация найдена!")
756
+ return self.best_solution
757
  else:
758
+ return self.best_solution or {"error": "Не удалось найти идеальную комбинацию"}
759
 
760
  except Exception as e:
761
  print(f"Ошибка при расчёте: {str(e)}")
762
  raise
763
 
764
+ def _backtrack_search(self, fertilizer_index=0, step=1.0):
765
+ self.current_depth += 1
766
+ if self.current_depth > self.max_recursion_depth:
 
 
 
 
 
767
  return False
768
 
769
+ # Проверяем текущее решение
770
+ current_diff = self._calculate_difference()
771
+ if current_diff < self.min_difference:
772
+ self.min_difference = current_diff
773
+ self.best_solution = {
774
+ "results": self._copy_results(),
775
+ "actual_profile": self.actual_profile.copy(),
776
+ "total_ec": self.total_ec,
777
+ "difference": current_diff
778
+ }
779
+
780
+ if current_diff < 0.1: # Допустимая погрешность
781
+ return True
782
+
783
+ # Пробуем добавлять удобрения с текущего индекса
784
  for i in range(fertilizer_index, len(self.fertilizer_list)):
785
  fert_name = self.fertilizer_list[i]
786
+
787
+ # Пробуем добавить удобрение с текущим шагом
788
+ self._apply_fertilizer(fert_name, step)
789
+
790
+ # Рекурсивно продолжаем поиск
791
+ if self._backtrack_search(i, step):
 
 
792
  return True
793
+
794
+ # Если не получилось - откатываемся
795
+ self._remove_fertilizer(fert_name, step)
796
+
797
+ # Пробуем уменьшить шаг для более точного поиска
798
+ if step > 0.1:
799
+ if self._backtrack_search(i, step/2):
800
+ return True
801
 
802
  return False
803
 
804
+ def _apply_fertilizer(self, fert_name, amount):
805
+ """Добавляет указанное количество удобрения"""
806
  if fert_name not in self.results:
807
+ self._init_fertilizer_record(fert_name)
808
+
809
+ self.results[fert_name]['граммы'] += amount
810
+ self.results[fert_name]['миллиграммы'] += int(amount * 1000)
811
+
812
+ added_ec = 0.0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
813
  for element, content in self.fertilizers[fert_name].items():
814
+ added_ppm = (amount * content * 1000) / self.volume
815
  self.results[fert_name][f'внесет {element}'] += added_ppm
816
  self.actual_profile[element] += added_ppm
817
+ added_ec += added_ppm * EC_COEFFICIENTS.get(element, 0.0015)
818
+
819
+ self.results[fert_name]['вклад в EC'] += added_ec
820
+ self.total_ec += added_ec
821
 
822
+ def _remove_fertilizer(self, fert_name, amount):
823
+ """Удаляет указанное количество удобрения"""
824
  if fert_name in self.results:
825
+ self.results[fert_name]['граммы'] -= amount
826
+ self.results[fert_name]['миллиграммы'] -= int(amount * 1000)
827
+
828
+ removed_ec = 0.0
829
  for element, content in self.fertilizers[fert_name].items():
830
+ removed_ppm = (amount * content * 1000) / self.volume
831
+ self.results[fert_name][f'внесет {element}'] -= removed_ppm
832
+ self.actual_profile[element] -= removed_ppm
833
+ removed_ec += removed_ppm * EC_COEFFICIENTS.get(element, 0.0015)
834
+
835
+ self.results[fert_name]['вклад в EC'] -= removed_ec
836
+ self.total_ec -= removed_ec
837
+
838
+ if self.results[fert_name]['граммы'] <= 0.001:
839
+ del self.results[fert_name]
840
+
841
+ def _calculate_difference(self):
842
+ """Вычисляет общее отклонение от целевого профиля"""
843
+ return sum(
844
+ abs(self.actual_profile[e] - self.target_profile[e])
845
+ for e in self.target_profile
846
+ )
847
 
848
+ def _copy_results(self):
849
+ """Создаёт глубокую копию результатов"""
850
+ return {
851
+ fert: data.copy()
852
+ for fert, data in self.results.items()
853
+ }
854
+
855
+ def _init_fertilizer_record(self, fert_name):
856
+ """Инициализирует запись для нового удобрения"""
857
+ self.results[fert_name] = {
858
+ 'граммы': 0.0,
859
+ 'миллиграммы': 0,
860
+ 'вклад в EC': 0.0
861
+ }
862
+ for elem in self.fertilizers[fert_name]:
863
+ self.results[fert_name][f'внесет {elem}'] = 0.0
864
 
865
  def calculate_ec(self):
866
  return round(self.total_ec, 2)