DmitrMakeev commited on
Commit
99d4fdf
·
verified ·
1 Parent(s): 80c4ba2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +172 -123
app.py CHANGED
@@ -693,6 +693,16 @@ def nutri_call():
693
 
694
 
695
 
 
 
 
 
 
 
 
 
 
 
696
 
697
 
698
 
@@ -711,10 +721,10 @@ VOLUME_LITERS = 100 # Объем раствора
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,8 +736,7 @@ NUTRIENT_CONTENT_IN_FERTILIZERS = {
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,153 +745,193 @@ 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:
 
693
 
694
 
695
 
696
+
697
+
698
+
699
+
700
+
701
+
702
+
703
+
704
+
705
+
706
 
707
 
708
 
 
721
 
722
  BASE_PROFILE = {
723
  "P": 50, # Фосфор
724
+ "K": 300, # Калий
725
  "Mg": 120, # Магний (высокий уровень)
726
  "Ca": 150, # Кальций
727
+ "S": 100, # Сера
728
  "N (NO3-)": 0, # Рассчитывается автоматически
729
  "N (NH4+)": 0 # Рассчитывается автоматически
730
  }
 
736
  "Аммоний азотнокислый": {"N (NO3-)": 0.17499, "N (NH4+)": 0.17499},
737
  "Сульфат магния": {"Mg": 0.09861, "S": 0.13010},
738
  "Монофосфат калия": {"P": 0.218, "K": 0.275},
739
+ "Сульфат кальция": {"Ca": 0.23, "S": 0.186}
 
740
  }
741
 
742
  EC_COEFFICIENTS = {
 
745
  'N (NO3-)': 0.0017, 'N (NH4+)': 0.0019
746
  }
747
 
 
 
748
  class NutrientCalculator:
749
+ def __init__(self, volume_liters=1.0, profile=BASE_PROFILE):
750
  self.volume = volume_liters
751
+ self.results = {}
752
+ self.target_profile = profile.copy()
753
+ self.actual_profile = {k: 0.0 for k in self.target_profile}
754
+ self.fertilizers = NUTRIENT_CONTENT_IN_FERTILIZERS
 
755
  self.total_ec = 0.0
 
 
 
 
756
 
757
  # Расчёт азота
758
  total_parts = NO3_RATIO + NH4_RATIO
759
  self.target_profile['N (NO3-)'] = TOTAL_NITROGEN * (NO3_RATIO / total_parts)
760
  self.target_profile['N (NH4+)'] = TOTAL_NITROGEN * (NH4_RATIO / total_parts)
761
 
762
+ # Сохраняем исходный профиль азота
763
+ self.initial_n_profile = {
764
+ "NO3-": self.target_profile['N (NO3-)'],
765
+ "NH4+": self.target_profile['N (NH4+)']
766
+ }
767
+
768
+ # Веса компенсации
769
+ self.compensation_weights = {
770
+ "Ca": {"weight": 0.3, "fert": "Сульфат кальция", "main_element": "Ca"},
771
+ "K": {"weight": 0.2, "fert": "Калий азотнокислый", "main_element": "K"},
772
+ "Mg": {"weight": 0.2, "fert": "Сульфат магния", "main_element": "Mg"},
773
+ "P": {"weight": 0.1, "fert": "Монофосфат калия", "main_element": "P"},
774
+ "S": {"weight": 0.1, "fert": "Калий сернокислый", "main_element": "S"},
775
+ "N (NO3-)": {"weight": 0.05, "fert": "Калий азотнокислый", "main_element": "N (NO3-)"},
776
+ "N (NH4+)": {"weight": 0.05, "fert": "Аммоний азотнокислый", "main_element": "N (NH4+)"}
777
+ }
778
+
779
+ def _label(self, element):
780
+ """Форматирование названий элементов для вывода"""
781
+ labels = {
782
+ 'N (NO3-)': 'NO3',
783
+ 'N (NH4+)': 'NH4'
784
+ }
785
+ return labels.get(element, element)
786
 
787
  def calculate(self):
788
  try:
789
+ # Первый проход: компенсация основных элементов
790
+ self._compensate_main_elements()
791
+
792
+ # Второй проход: компенсация азота
793
+ self._compensate_nitrogen()
794
+
795
+ # Третий проход: компенсация второстепенных элементов
796
+ self._compensate_secondary_elements()
 
 
797
 
798
+ # Четвертый проход: корректировка перебора
799
+ self._adjust_overages()
800
+
801
+ return self.results
802
  except Exception as e:
803
  print(f"Ошибка при расчёте: {str(e)}")
804
  raise
805
 
806
+ def _compensate_main_elements(self):
807
+ """Компенсация основных элементов (Ca, Mg, P)"""
808
+ for element, weight_data in self.compensation_weights.items():
809
+ if element in ["Ca", "Mg", "P"]:
810
+ fert_name = weight_data["fert"]
811
+ main_element = weight_data["main_element"]
812
+ required_ppm = self.target_profile[main_element] - self.actual_profile[main_element]
813
+ if required_ppm > 0.1:
814
+ self._apply_with_limit(fert_name, main_element, required_ppm)
815
+
816
+ def _compensate_nitrogen(self):
817
+ """Компенсация азота (NO3-, NH4+)"""
818
+ for nitrogen_type in ["N (NO3-)", "N (NH4+)"]:
819
+ required_ppm = self.target_profile[nitrogen_type] - self.actual_profile[nitrogen_type]
820
+ if required_ppm > 0.1:
821
+ fert_name = self.compensation_weights[nitrogen_type]["fert"]
822
+ self._apply_with_limit(fert_name, nitrogen_type, required_ppm)
823
+
824
+ def _compensate_secondary_elements(self):
825
+ """Компенсация второстепенных элементов (K, S)"""
826
+ for element, weight_data in self.compensation_weights.items():
827
+ if element in ["K", "S"]:
828
+ fert_name = weight_data["fert"]
829
+ main_element = weight_data["main_element"]
830
+ required_ppm = self.target_profile[main_element] - self.actual_profile[main_element]
831
+ if required_ppm > 0.1:
832
+ self._apply_with_limit(fert_name, main_element, required_ppm)
833
+
834
+ def _apply_with_limit(self, fert_name, main_element, required_ppm):
835
+ """Применение удобрения с ограничением по перебору"""
836
+ if required_ppm <= 0:
837
+ return
838
 
839
+ try:
840
+ content = self.fertilizers[fert_name][main_element]
841
+ max_allowed_ppm = self.target_profile[main_element] - self.actual_profile[main_element]
842
+ grams = min((required_ppm * self.volume) / (content * 1000), (max_allowed_ppm * self.volume) / (content * 1000))
 
 
843
 
844
+ if fert_name not in self.results:
845
+ result = {
846
+ 'граммы': 0.0,
847
+ 'миллиграммы': 0,
848
+ 'вклад в EC': 0.0
849
+ }
850
+ for element in self.fertilizers[fert_name]:
851
+ result[f'внесет {self._label(element)}'] = 0.0
852
+ self.results[fert_name] = result
853
 
854
+ self.results[fert_name]['граммы'] += grams
855
+ self.results[fert_name]['миллиграммы'] += int(grams * 1000)
856
 
857
+ fert_ec = 0.0
858
+ for element, percent in self.fertilizers[fert_name].items():
859
+ added_ppm = (grams * percent * 1000) / self.volume
860
+ self.results[fert_name][f'внесет {self._label(element)}'] += added_ppm
861
+ self.actual_profile[element] += added_ppm
862
+ fert_ec += added_ppm * EC_COEFFICIENTS.get(element, 0.0015)
863
+
864
+ self.results[fert_name]['вклад в EC'] += fert_ec
865
+ self.total_ec += fert_ec
866
+ except KeyError as e:
867
+ print(f"Ошибка: отсутствует элемент {str(e)} в удобрении {fert_name}")
868
+ raise
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
869
 
870
+ def _adjust_overages(self):
871
+ """Корректировка перебора элементов"""
872
+ for element in self.actual_profile:
873
+ if self.actual_profile[element] > self.target_profile[element]:
874
+ overage = self.actual_profile[element] - self.target_profile[element]
875
+ self.actual_profile[element] -= overage
876
+ print(f"Корректировка перебора: {element} уменьшен на {overage:.2f} ppm")
877
 
878
+ def calculate_ec(self):
879
+ return round(self.total_ec, 2)
 
 
880
 
881
+ def print_initial_nitrogen_report(self):
 
882
  try:
883
+ print("Исходный расчёт азота:")
884
+ print(f" NO3-: {self.initial_n_profile['NO3-']} ppm")
885
+ print(f" NH4+: {self.initial_n_profile['NH4+']} ppm")
886
+ except Exception as e:
887
+ print(f"Ошибка при выводе отчёта: {str(e)}")
888
+ raise
889
+ def print_report(self):
890
+ try:
891
+ print("\n" + "="*60)
892
+ print("ПРОФИЛЬ ПИТАТЕЛЬНОГО РАСТВОРА (ИТОГО):")
893
+ print("="*60)
894
+ table = [[el, round(self.actual_profile[el], 1)] for el in self.actual_profile]
895
+ print(tabulate(table, headers=["Элемент", "ppm"]))
896
+
897
+ print("\nИсходный расчёт азота:")
898
+ for form, val in self.initial_n_profile.items():
899
+ print(f" {form}: {round(val, 1)} ppm")
900
+
901
+ print("\n" + "="*60)
902
+ print(f"РАСЧЕТ ДЛЯ {self.volume} ЛИТРОВ РАСТВОРА")
903
+ print("="*60)
904
+ print(f"Общая концентрация: {round(sum(self.actual_profile.values()), 1)} ppm")
905
+ print(f"EC: {self.calculate_ec()} mS/cm")
906
+
907
+ print("\nРЕКОМЕНДУЕМЫЕ УДОБРЕНИЯ:")
908
+ fert_table = []
909
+ for fert, data in self.results.items():
910
+ adds = [f"+{k}: {v:.1f} ppm" for k, v in data.items() if k.startswith('внесет')]
911
+ fert_table.append([
912
+ fert,
913
+ round(data['граммы'], 3),
914
+ data['миллиграммы'],
915
+ round(data['вклад в EC'], 3),
916
+ "\n".join(adds)
917
+ ])
918
+ print(tabulate(fert_table,
919
+ headers=["Удобрение", "Граммы", "Миллиграммы", "EC (мСм/см)", "Добавит"]))
920
+
921
+ print("\nОСТАТОЧНЫЙ ДЕФИЦИТ:")
922
+ deficit = {
923
+ k: round(self.target_profile[k] - self.actual_profile[k], 1)
924
+ for k in self.target_profile
925
+ if abs(self.target_profile[k] - self.actual_profile[k]) > 0.1
926
+ }
927
+ if deficit:
928
+ for el, val in deficit.items():
929
+ print(f" {el}: {val} ppm")
930
+ else:
931
+ print(" Все элементы покрыты полностью")
932
  except Exception as e:
933
  print(f"Ошибка при выводе отчёта: {str(e)}")
934
  raise
 
 
935
 
936
  if __name__ == "__main__":
937
  try: