DmitrMakeev commited on
Commit
80c4ba2
·
verified ·
1 Parent(s): 689b83c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +123 -163
app.py CHANGED
@@ -701,7 +701,6 @@ def nutri_call():
701
 
702
 
703
 
704
-
705
  from tabulate import tabulate
706
 
707
  # Глобальные параметры
@@ -712,10 +711,10 @@ VOLUME_LITERS = 100 # Объем раствора
712
 
713
  BASE_PROFILE = {
714
  "P": 50, # Фосфор
715
- "K": 300, # Калий
716
  "Mg": 120, # Магний (высокий уровень)
717
  "Ca": 150, # Кальций
718
- "S": 100, # Сера
719
  "N (NO3-)": 0, # Рассчитывается автоматически
720
  "N (NH4+)": 0 # Рассчитывается автоматически
721
  }
@@ -727,7 +726,8 @@ NUTRIENT_CONTENT_IN_FERTILIZERS = {
727
  "Аммоний азотнокислый": {"N (NO3-)": 0.17499, "N (NH4+)": 0.17499},
728
  "Сульфат магния": {"Mg": 0.09861, "S": 0.13010},
729
  "Монофосфат калия": {"P": 0.218, "K": 0.275},
730
- "Сульфат кальция": {"Ca": 0.23, "S": 0.186}
 
731
  }
732
 
733
  EC_COEFFICIENTS = {
@@ -736,194 +736,154 @@ EC_COEFFICIENTS = {
736
  'N (NO3-)': 0.0017, 'N (NH4+)': 0.0019
737
  }
738
 
 
 
739
  class NutrientCalculator:
740
- def __init__(self, volume_liters=1.0, profile=BASE_PROFILE):
741
  self.volume = volume_liters
742
- self.results = {}
743
- self.target_profile = profile.copy()
744
- self.actual_profile = {k: 0.0 for k in self.target_profile}
745
- self.fertilizers = NUTRIENT_CONTENT_IN_FERTILIZERS
 
746
  self.total_ec = 0.0
 
 
 
 
747
 
748
  # Расчёт азота
749
  total_parts = NO3_RATIO + NH4_RATIO
750
  self.target_profile['N (NO3-)'] = TOTAL_NITROGEN * (NO3_RATIO / total_parts)
751
  self.target_profile['N (NH4+)'] = TOTAL_NITROGEN * (NH4_RATIO / total_parts)
752
 
753
- # Сохраняем исходный профиль азота
754
- self.initial_n_profile = {
755
- "NO3-": self.target_profile['N (NO3-)'],
756
- "NH4+": self.target_profile['N (NH4+)']
757
- }
758
-
759
- # Веса компенсации
760
- self.compensation_weights = {
761
- "Ca": {"weight": 0.3, "fert": "Сульфат кальция", "main_element": "Ca"},
762
- "K": {"weight": 0.2, "fert": "Калий азотнокислый", "main_element": "K"},
763
- "Mg": {"weight": 0.2, "fert": "Сульфат магния", "main_element": "Mg"},
764
- "P": {"weight": 0.1, "fert": "Монофосфат калия", "main_element": "P"},
765
- "S": {"weight": 0.1, "fert": "Калий сернокислый", "main_element": "S"},
766
- "N (NO3-)": {"weight": 0.05, "fert": "Калий азотнокислый", "main_element": "N (NO3-)"},
767
- "N (NH4+)": {"weight": 0.05, "fert": "Аммоний азотнокислый", "main_element": "N (NH4+)"}
768
- }
769
-
770
- def _label(self, element):
771
- """Форматирование названий элементов для вывода"""
772
- labels = {
773
- 'N (NO3-)': 'NO3',
774
- 'N (NH4+)': 'NH4'
775
- }
776
- return labels.get(element, element)
777
 
778
  def calculate(self):
779
  try:
780
- # Первый проход: компенсация основных элементов
781
- self._compensate_main_elements()
782
-
783
- # Второй проход: компенсация азота
784
- self._compensate_nitrogen()
785
-
786
- # Третий проход: компенсация второстепенных элементов
787
- self._compensate_secondary_elements()
788
-
789
- # Четвертый проход: корректировка перебора
790
- self._adjust_overages()
791
 
792
- return self.results
793
  except Exception as e:
794
  print(f"Ошибка при расчёте: {str(e)}")
795
  raise
796
 
797
- def _compensate_main_elements(self):
798
- """Компенсация основных элементов (Ca, Mg, P)"""
799
- for element, weight_data in self.compensation_weights.items():
800
- if element in ["Ca", "Mg", "P"]:
801
- fert_name = weight_data["fert"]
802
- main_element = weight_data["main_element"]
803
- required_ppm = self.target_profile[main_element] - self.actual_profile[main_element]
804
- if required_ppm > 0.1:
805
- self._apply_with_limit(fert_name, main_element, required_ppm)
806
-
807
- def _compensate_nitrogen(self):
808
- """Компенсация азота (NO3-, NH4+)"""
809
- for nitrogen_type in ["N (NO3-)", "N (NH4+)"]:
810
- required_ppm = self.target_profile[nitrogen_type] - self.actual_profile[nitrogen_type]
811
- if required_ppm > 0.1:
812
- fert_name = self.compensation_weights[nitrogen_type]["fert"]
813
- self._apply_with_limit(fert_name, nitrogen_type, required_ppm)
814
-
815
- def _compensate_secondary_elements(self):
816
- """Компенсация второстепенных элементов (K, S)"""
817
- for element, weight_data in self.compensation_weights.items():
818
- if element in ["K", "S"]:
819
- fert_name = weight_data["fert"]
820
- main_element = weight_data["main_element"]
821
- required_ppm = self.target_profile[main_element] - self.actual_profile[main_element]
822
- if required_ppm > 0.1:
823
- self._apply_with_limit(fert_name, main_element, required_ppm)
824
-
825
- def _apply_with_limit(self, fert_name, main_element, required_ppm):
826
- """Применение удобрения с ограничением по перебору"""
827
- if required_ppm <= 0:
828
- return
829
 
830
- try:
831
- content = self.fertilizers[fert_name][main_element]
832
- max_allowed_ppm = self.target_profile[main_element] - self.actual_profile[main_element]
833
- grams = min((required_ppm * self.volume) / (content * 1000), (max_allowed_ppm * self.volume) / (content * 1000))
 
 
 
 
834
 
835
- if fert_name not in self.results:
836
- result = {
837
- 'граммы': 0.0,
838
- 'миллиграммы': 0,
839
- 'вклад в EC': 0.0
840
- }
841
- for element in self.fertilizers[fert_name]:
842
- result[f'внесет {self._label(element)}'] = 0.0
843
- self.results[fert_name] = result
844
-
845
- self.results[fert_name]['граммы'] += grams
846
- self.results[fert_name]['миллиграммы'] += int(grams * 1000)
847
 
848
- fert_ec = 0.0
849
- for element, percent in self.fertilizers[fert_name].items():
850
- added_ppm = (grams * percent * 1000) / self.volume
851
- self.results[fert_name][f'внесет {self._label(element)}'] += added_ppm
852
- self.actual_profile[element] += added_ppm
853
- fert_ec += added_ppm * EC_COEFFICIENTS.get(element, 0.0015)
854
 
855
- self.results[fert_name]['вклад в EC'] += fert_ec
856
- self.total_ec += fert_ec
857
- except KeyError as e:
858
- print(f"Ошибка: отсутствует элемент {str(e)} в удобрении {fert_name}")
859
- raise
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
860
 
861
- def _adjust_overages(self):
862
- """Корректировка перебора элементов"""
863
- for element in self.actual_profile:
864
- if self.actual_profile[element] > self.target_profile[element]:
865
- overage = self.actual_profile[element] - self.target_profile[element]
866
- self.actual_profile[element] -= overage
867
- print(f"Корректировка перебора: {element} уменьшен на {overage:.2f} ppm")
868
 
869
- def calculate_ec(self):
870
- return round(self.total_ec, 2)
 
871
 
872
- def print_initial_nitrogen_report(self):
873
- try:
874
- print("Исходный расчёт азота:")
875
- print(f" NO3-: {self.initial_n_profile['NO3-']} ppm")
876
- print(f" NH4+: {self.initial_n_profile['NH4+']} ppm")
877
- except Exception as e:
878
- print(f"Ошибка при выводе отчёта: {str(e)}")
879
- raise
880
- def print_report(self):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
881
  try:
882
- print("\n" + "="*60)
883
- print("ПРОФИЛЬ ПИТАТЕЛЬНОГО РАСТВОРА (ИТОГО):")
884
- print("="*60)
885
- table = [[el, round(self.actual_profile[el], 1)] for el in self.actual_profile]
886
- print(tabulate(table, headers=["Элемент", "ppm"]))
887
-
888
- print("\nИсходный расчёт азота:")
889
- for form, val in self.initial_n_profile.items():
890
- print(f" {form}: {round(val, 1)} ppm")
891
-
892
- print("\n" + "="*60)
893
- print(f"РАСЧЕТ ДЛЯ {self.volume} ЛИТРОВ РАСТВОРА")
894
- print("="*60)
895
- print(f"Общая концентрация: {round(sum(self.actual_profile.values()), 1)} ppm")
896
- print(f"EC: {self.calculate_ec()} mS/cm")
897
-
898
- print("\nРЕКОМЕНДУЕМЫЕ УДОБРЕНИЯ:")
899
- fert_table = []
900
- for fert, data in self.results.items():
901
- adds = [f"+{k}: {v:.1f} ppm" for k, v in data.items() if k.startswith('внесет')]
902
- fert_table.append([
903
- fert,
904
- round(data['граммы'], 3),
905
- data['миллиграммы'],
906
- round(data['вклад в EC'], 3),
907
- "\n".join(adds)
908
- ])
909
- print(tabulate(fert_table,
910
- headers=["Удобрение", "Граммы", "Миллиграммы", "EC (мСм/см)", "Добавит"]))
911
-
912
- print("\nОСТАТОЧНЫЙ ДЕФИЦИТ:")
913
- deficit = {
914
- k: round(self.target_profile[k] - self.actual_profile[k], 1)
915
- for k in self.target_profile
916
- if abs(self.target_profile[k] - self.actual_profile[k]) > 0.1
917
- }
918
- if deficit:
919
- for el, val in deficit.items():
920
- print(f" {el}: {val} ppm")
921
- else:
922
- print(" Все элементы покрыты полностью")
923
  except Exception as e:
924
  print(f"Ошибка при выводе отчёта: {str(e)}")
925
  raise
926
 
 
 
927
  if __name__ == "__main__":
928
  try:
929
  calculator = NutrientCalculator(volume_liters=VOLUME_LITERS)
 
701
 
702
 
703
 
 
704
  from tabulate import tabulate
705
 
706
  # Глобальные параметры
 
711
 
712
  BASE_PROFILE = {
713
  "P": 50, # Фосфор
714
+ "K": 210, # Калий
715
  "Mg": 120, # Магний (высокий уровень)
716
  "Ca": 150, # Кальций
717
+ "S": 50, # Сера
718
  "N (NO3-)": 0, # Рассчитывается автоматически
719
  "N (NH4+)": 0 # Рассчитывается автоматически
720
  }
 
726
  "Аммоний азотнокислый": {"N (NO3-)": 0.17499, "N (NH4+)": 0.17499},
727
  "Сульфат магния": {"Mg": 0.09861, "S": 0.13010},
728
  "Монофосфат калия": {"P": 0.218, "K": 0.275},
729
+ "Сульфат кальция": {"Ca": 0.23, "S": 0.186},
730
+ "Кольцевая селитра": {"N (NO3-)": 0.15, "Ca": 0.20} # Новое удобрение
731
  }
732
 
733
  EC_COEFFICIENTS = {
 
736
  'N (NO3-)': 0.0017, 'N (NH4+)': 0.0019
737
  }
738
 
739
+
740
+
741
  class NutrientCalculator:
742
+ def __init__(self, volume_liters=1.0):
743
  self.volume = volume_liters
744
+ self.target_profile = BASE_PROFILE.copy()
745
+ self.fertilizers = {
746
+ name: Composition.from_dict({name: content})
747
+ for name, content in NUTRIENT_CONTENT_IN_FERTILIZERS.items()
748
+ }
749
  self.total_ec = 0.0
750
+ self.best_solution = None
751
+ self.min_difference = float('inf')
752
+ self.max_recursion_depth = 1000
753
+ self.current_depth = 0
754
 
755
  # Расчёт азота
756
  total_parts = NO3_RATIO + NH4_RATIO
757
  self.target_profile['N (NO3-)'] = TOTAL_NITROGEN * (NO3_RATIO / total_parts)
758
  self.target_profile['N (NH4+)'] = TOTAL_NITROGEN * (NH4_RATIO / total_parts)
759
 
760
+ # Целевой профиль как объект Composition
761
+ self.target_composition = Composition('Target Profile', list(self.target_profile.values()))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
762
 
763
  def calculate(self):
764
  try:
765
+ self.actual_profile = {k: 0.0 for k in self.target_profile}
766
+ self.results = {}
767
+ self.current_depth = 0
768
+
769
+ if self._backtrack_search():
770
+ print("Оптимальная комбинация найдена!")
771
+ return self.best_solution
772
+ else:
773
+ print("Идеальное решение не найдено. Возвращаю лучшее найденное решение.")
774
+ return self.best_solution or {"error": "Не удалось найти подходящую комбинацию"}
 
775
 
 
776
  except Exception as e:
777
  print(f"Ошибка при расчёте: {str(e)}")
778
  raise
779
 
780
+ def _backtrack_search(self, fertilizer_index=0, step=1.0):
781
+ self.current_depth += 1
782
+ if self.current_depth > self.max_recursion_depth:
783
+ return False
784
+
785
+ # Текущий профиль как объект Composition
786
+ current_composition = Composition('Current Profile', list(self.actual_profile.values()))
787
+ current_diff = self._calculate_difference(current_composition)
788
+
789
+ if current_diff < self.min_difference:
790
+ self.min_difference = current_diff
791
+ self.best_solution = {
792
+ "results": self._copy_results(),
793
+ "actual_profile": self.actual_profile.copy(),
794
+ "total_ec": self.total_ec,
795
+ "difference": current_diff
796
+ }
797
+
798
+ if current_diff < 1.0: # Допустимая погрешность
799
+ return True
 
 
 
 
 
 
 
 
 
 
 
 
800
 
801
+ # Пробуем добавлять удобрения с текущего индекса
802
+ for i in range(fertilizer_index, len(self.fertilizers)):
803
+ fert_name = list(self.fertilizers.keys())[i]
804
+ fert_composition = self.fertilizers[fert_name]
805
+
806
+ # Проверяем, можно ли применить удобрение
807
+ if not self._can_apply_fertilizer(fert_composition):
808
+ continue
809
 
810
+ # Пробуем добавить удобрение с текущим шагом
811
+ self._apply_fertilizer(fert_name, step)
 
 
 
 
 
 
 
 
 
 
812
 
813
+ # Рекурсивно продолжаем поиск
814
+ if self._backtrack_search(i, step):
815
+ return True
 
 
 
816
 
817
+ # Если не получилось - откатываемся
818
+ self._remove_fertilizer(fert_name, step)
819
+
820
+ # Пробуем уменьшить шаг для более точного поиска
821
+ if step > 0.1 and current_diff > 5.0:
822
+ if self._backtrack_search(i, step / 2):
823
+ return True
824
+
825
+ return False
826
+
827
+ def _can_apply_fertilizer(self, fert_composition):
828
+ """Проверяет, можно ли применить удобрение без перебора"""
829
+ for element, content in zip(nutrients_stencil, fert_composition.vector):
830
+ added_ppm = (1 * content * 1000) / self.volume
831
+ if self.actual_profile[element] + added_ppm > self.target_profile[element] + 1.0:
832
+ return False
833
+ return True
834
+
835
+ def _apply_fertilizer(self, fert_name, amount):
836
+ """Добавляет указанное количество удобрения"""
837
+ fert_composition = self.fertilizers[fert_name]
838
+ scaled_composition = amount * fert_composition
839
+
840
+ if fert_name not in self.results:
841
+ self.results[fert_name] = {
842
+ 'граммы': 0.0,
843
+ 'миллиграммы': 0,
844
+ 'вклад в EC': 0.0
845
+ }
846
 
847
+ self.results[fert_name]['граммы'] += amount
848
+ self.results[fert_name]['миллиграммы'] += int(amount * 1000)
 
 
 
 
 
849
 
850
+ for i, nutrient in enumerate(nutrients_stencil):
851
+ added_ppm = scaled_composition.vector[i] * 1000 / self.volume
852
+ self.actual_profile[nutrient] += added_ppm
853
 
854
+ def _remove_fertilizer(self, fert_name, amount):
855
+ """Удаляет указанное количество удобрения"""
856
+ fert_composition = self.fertilizers[fert_name]
857
+ scaled_composition = amount * fert_composition
858
+
859
+ if fert_name in self.results:
860
+ self.results[fert_name]['граммы'] -= amount
861
+ self.results[fert_name]['миллиграммы'] -= int(amount * 1000)
862
+
863
+ for i, nutrient in enumerate(nutrients_stencil):
864
+ removed_ppm = scaled_composition.vector[i] * 1000 / self.volume
865
+ self.actual_profile[nutrient] -= removed_ppm
866
+
867
+ if self.results[fert_name]['граммы'] <= 0.001:
868
+ del self.results[fert_name]
869
+
870
+ def _calculate_difference(self, current_composition):
871
+ """Вычисляет общее отклонение от целевого профиля"""
872
+ diff_vector = self.target_composition.vector - current_composition.vector
873
+ return np.sum(np.abs(diff_vector))
874
+
875
+ def generate_report(self):
876
+ """Генерация отчета о питательном растворе"""
877
  try:
878
+ actual_composition = Composition('Actual Profile', list(self.actual_profile.values()))
879
+ report = actual_composition.table(sparse=True, ref=self.target_composition)
880
+ return report
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
881
  except Exception as e:
882
  print(f"Ошибка при выводе отчёта: {str(e)}")
883
  raise
884
 
885
+
886
+
887
  if __name__ == "__main__":
888
  try:
889
  calculator = NutrientCalculator(volume_liters=VOLUME_LITERS)