DmitrMakeev commited on
Commit
689b83c
·
verified ·
1 Parent(s): 9e57729

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +194 -147
app.py CHANGED
@@ -692,21 +692,30 @@ def nutri_call():
692
 
693
 
694
 
 
 
 
 
 
 
 
 
 
 
695
  from tabulate import tabulate
696
- import numpy as np
697
 
698
  # Глобальные параметры
699
- TOTAL_NITROGEN = 120.0 # Общее количество азота
700
  NO3_RATIO = 8.0 # Соотношение NO3:NH4
701
- NH4_RATIO = 1.0 # Соотношение NH4:NO3
702
- VOLUME_LITERS = 100 # Объем раствора
703
 
704
  BASE_PROFILE = {
705
  "P": 50, # Фосфор
706
- "K": 210, # Калий
707
  "Mg": 120, # Магний (высокий уровень)
708
  "Ca": 150, # Кальций
709
- "S": 50, # Сера
710
  "N (NO3-)": 0, # Рассчитывается автоматически
711
  "N (NH4+)": 0 # Рассчитывается автоматически
712
  }
@@ -718,172 +727,210 @@ NUTRIENT_CONTENT_IN_FERTILIZERS = {
718
  "Аммоний азотнокислый": {"N (NO3-)": 0.17499, "N (NH4+)": 0.17499},
719
  "Сульфат магния": {"Mg": 0.09861, "S": 0.13010},
720
  "Монофосфат калия": {"P": 0.218, "K": 0.275},
721
- "Сульфат кальция": {"Ca": 0.23, "S": 0.186},
722
- "Кольцевая селитра": {"N (NO3-)": 0.15, "Ca": 0.20} # Новое удобрение
723
  }
724
 
725
  EC_COEFFICIENTS = {
726
- 'P': 0.0012, 'K': 0.0018, 'Mg': 0.0015,
727
- 'Ca': 0.0016, 'S': 0.0014,
728
  'N (NO3-)': 0.0017, 'N (NH4+)': 0.0019
729
  }
 
730
  class NutrientCalculator:
731
- def __init__(self, volume_liters=1.0):
732
  self.volume = volume_liters
733
- self.target_profile = BASE_PROFILE.copy()
734
- self._init_nitrogen()
735
- self.fertilizers = NUTRIENT_CONTENT_IN_FERTILIZERS
736
  self.results = {}
 
737
  self.actual_profile = {k: 0.0 for k in self.target_profile}
 
738
  self.total_ec = 0.0
739
- self.tolerance = 1.0 # Допустимое отклонение в ppm
740
- self.max_iterations = 100 # Максимальное число итераций
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
741
 
742
  def calculate(self):
743
- # Оптимизированный порядок внесения элементов
744
- priority_order = [
745
- ('Ca', ['Кальциевая селитра', 'Сульфат кальция']),
746
- ('N (NH4+)', ['Аммоний азотнокислый']),
747
- ('P', ['Монофосфат калия']),
748
- ('Mg', ['Сульфат магния']),
749
- ('N (NO3-)', ['Калий азотнокислый', 'Кальциевая селитра']),
750
- ('K', ['Калий азотнокислый', 'Калий сернокислый']),
751
- ('S', ['Калий сернокислый', 'Сульфат магния'])
752
- ]
753
-
754
- for _ in range(self.max_iterations):
755
- for element, fert_options in priority_order:
756
- self._balance_element(element, fert_options)
757
-
758
- if self._is_balanced():
759
- break
760
-
761
- # Финальная тонкая настройка
762
- self._fine_tune()
763
-
764
- return self._prepare_results()
765
 
766
- def _balance_element(self, element, fert_options):
767
- deficit = self.target_profile[element] - self.actual_profile[element]
768
- if deficit <= self.tolerance:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
769
  return
 
 
 
 
 
770
 
771
- for fert_name in fert_options:
772
- content = self.fertilizers[fert_name].get(element, 0)
773
- if content == 0:
774
- continue
775
-
776
- # Рассчитываем возможное количество без перебора других элементов
777
- max_possible = min(
778
- deficit / content,
779
- *[(self.target_profile[e] - self.actual_profile[e]) / self.fertilizers[fert_name].get(e, 1)
780
- for e in self.fertilizers[fert_name] if e != element]
781
- )
782
 
783
- if max_possible > 0:
784
- grams = (max_possible * 1000) / self.volume
785
- self._apply_fertilizer(fert_name, grams)
786
- break
787
-
788
- def _fine_tune(self):
789
- # Тонкая подстройка малыми шагами (0.1 грамма)
790
- for _ in range(20):
791
- worst_element = max(
792
- self.target_profile.keys(),
793
- key=lambda x: abs(self.target_profile[x] - self.actual_profile[x])
794
- )
795
- deficit = self.target_profile[worst_element] - self.actual_profile[worst_element]
796
 
797
- if abs(deficit) <= self.tolerance:
798
- break
799
-
800
- # Ищем уд��брение, содержащее этот элемент
801
- for fert_name, contents in self.fertilizers.items():
802
- if worst_element in contents:
803
- small_step = 0.1 # грамм
804
- self._apply_fertilizer(fert_name, small_step)
805
- break
806
-
807
- def _is_balanced(self):
808
- return all(
809
- abs(self.target_profile[e] - self.actual_profile[e]) <= self.tolerance
810
- for e in self.target_profile
811
- )
812
-
813
- def _apply_fertilizer(self, fert_name, grams):
814
- if fert_name not in self.results:
815
- self.results[fert_name] = {
816
- 'граммы': 0.0,
817
- 'вклад': {e: 0.0 for e in self.fertilizers[fert_name]}
818
- }
819
-
820
- self.results[fert_name]['граммы'] += grams
821
-
822
- for element, content in self.fertilizers[fert_name].items():
823
- added_ppm = (grams * content * 1000) / self.volume
824
- self.actual_profile[element] += added_ppm
825
- self.results[fert_name]['вклад'][element] += added_ppm
826
- self.total_ec += added_ppm * EC_COEFFICIENTS.get(element, 0.0015)
827
-
828
- def _fine_tuning(self):
829
- # Тонкая подстройка малыми шагами (0.1 грамма)
830
- for _ in range(100): # Максимум 100 итераций
831
- worst_element = self._get_worst_balanced()
832
- if not worst_element:
833
- break
834
-
835
- self._balance_element(worst_element)
836
-
837
- def _get_worst_balanced(self):
838
- worst_element = None
839
- max_diff = 0
840
-
841
- for element in self.target_profile:
842
- diff = abs(self.target_profile[element] - self.actual_profile[element])
843
- if diff > max_diff and diff > self.tolerance:
844
- max_diff = diff
845
- worst_element = element
846
 
847
- return worst_element
 
 
 
 
848
 
849
- def _prepare_results(self):
850
- deficits = {
851
- k: round(self.target_profile[k] - self.actual_profile[k], 3)
852
- for k in self.target_profile
853
- if abs(self.target_profile[k] - self.actual_profile[k]) > self.tolerance
854
- }
855
-
856
- return {
857
- "actual_profile": {k: round(v, 3) for k, v in self.actual_profile.items()},
858
- "fertilizers": self._format_fertilizers(),
859
- "total_ec": round(self.total_ec, 3),
860
- "total_ppm": round(sum(self.actual_profile.values()), 3),
861
- "deficits": deficits
862
- }
863
 
864
- def _format_fertilizers(self):
865
- formatted = {}
866
- for name, data in self.results.items():
867
- formatted[name] = {
868
- 'граммы': round(data['граммы'], 3),
869
- 'миллиграммы': int(data['граммы'] * 1000),
870
- 'вклад в EC': round(sum(
871
- v * EC_COEFFICIENTS.get(k, 0.0015)
872
- for k, v in data['вклад'].items()
873
- ), 3),
874
- 'добавит': [f"{k}: {round(v, 3)} ppm" for k, v in data['вклад'].items()]
875
- }
876
- return formatted
877
 
878
- def generate_report(self):
879
- """Генерация отчета о питательном растворе"""
880
  try:
881
- actual_composition = Composition('Actual Profile', list(self.actual_profile.values()))
882
- report = actual_composition.table(sparse=True, ref=self.target_composition)
883
- return report
884
  except Exception as e:
885
  print(f"Ошибка пр�� выводе отчёта: {str(e)}")
886
  raise
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
887
 
888
 
889
 
 
692
 
693
 
694
 
695
+
696
+
697
+
698
+
699
+
700
+
701
+
702
+
703
+
704
+
705
  from tabulate import tabulate
 
706
 
707
  # Глобальные параметры
708
+ TOTAL_NITROGEN = 120.0 # Общее количество азота
709
  NO3_RATIO = 8.0 # Соотношение NO3:NH4
710
+ NH4_RATIO = 1.00 # Соотношение NH4:NO3
711
+ 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
  "Аммоний азотнокислый": {"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 = {
734
+ 'P': 0.0012, 'K': 0.0018, 'Mg': 0.0015,
735
+ 'Ca': 0.0016, 'S': 0.0014,
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)
930
+ calculator.calculate()
931
+ calculator.print_report() # Правильный вызов метода класса
932
+ except Exception as e:
933
+ print(f"Критическая ошибка: {str(e)}")
934
 
935
 
936