DmitrMakeev commited on
Commit
f5fb09b
·
verified ·
1 Parent(s): be13947

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +240 -147
app.py CHANGED
@@ -693,178 +693,271 @@ def nutri_call():
693
 
694
 
695
 
696
- from tabulate import tabulate
697
  import numpy as np
 
698
 
699
  # Глобальные параметры
700
- TOTAL_NITROGEN = 120.0
701
- NO3_RATIO = 8.0
702
- NH4_RATIO = 1.0
703
- VOLUME_LITERS = 100
704
 
705
- # Базовый профиль
706
  BASE_PROFILE = {
707
- "P": 50, "K": 210, "Mg": 120,
708
- "Ca": 150, "S": 50,
709
- "N (NO3-)": 0, "N (NH4+)": 0
 
 
 
 
710
  }
711
 
712
- # Состав удобрений (в долях)
713
- NUTRIENT_CONTENT = {
714
  "Кальциевая селитра": {"N (NO3-)": 0.11863, "Ca": 0.16972},
715
  "Калий азотнокислый": {"N (NO3-)": 0.136, "K": 0.382},
716
  "Калий сернокислый": {"K": 0.44874, "S": 0.18401},
717
  "Аммоний азотнокислый": {"N (NO3-)": 0.17499, "N (NH4+)": 0.17499},
718
  "Сульфат магния": {"Mg": 0.09861, "S": 0.13010},
719
  "Монофосфат калия": {"P": 0.218, "K": 0.275},
720
- "Сульфат кальция": {"Ca": 0.23, "S": 0.186}
 
721
  }
722
 
723
- # Коэффициенты EC
724
- EC_COEFF = {
725
- 'P': 0.0012, 'K': 0.0018, 'Mg': 0.0015,
726
- 'Ca': 0.0016, 'S': 0.0014,
727
  'N (NO3-)': 0.0017, 'N (NH4+)': 0.0019
728
  }
729
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
730
  class NutrientCalculator:
731
- def __init__(self, volume=1.0):
732
- self.volume = volume
733
- self.target = BASE_PROFILE.copy()
734
- self._init_nitrogen()
735
- self.results = {}
736
- self.actual = {k: 0.0 for k in self.target}
 
737
  self.total_ec = 0.0
738
-
739
- def _init_nitrogen(self):
740
- total = NO3_RATIO + NH4_RATIO
741
- self.target['N (NO3-)'] = TOTAL_NITROGEN * (NO3_RATIO / total)
742
- self.target['N (NH4+)'] = TOTAL_NITROGEN * (NH4_RATIO / total)
743
-
 
 
 
 
 
 
 
744
  def calculate(self):
745
- # 1. Вносим кальций (приоритет Ca(NO3)2)
746
- ca_needed = self.target['Ca'] - self.actual['Ca']
747
- if ca_needed > 0:
748
- self._apply_fert("Кальциевая селитра", "Ca", ca_needed)
749
-
750
- # 2. Вносим NH4
751
- nh4_needed = self.target['N (NH4+)'] - self.actual['N (NH4+)']
752
- if nh4_needed > 0:
753
- self._apply_fert("Аммоний азотнокислый", "N (NH4+)", nh4_needed)
754
-
755
- # 3. Компенсируем NO3
756
- no3_needed = self.target['N (NO3-)'] - self.actual['N (NO3-)']
757
- if no3_needed > 0:
758
- self._apply_fert("Калий азотнокислый", "N (NO3-)", no3_needed)
759
-
760
- # 4. Вносим фосфор
761
- p_needed = self.target['P'] - self.actual['P']
762
- if p_needed > 0:
763
- self._apply_fert("Монофосфат калия", "P", p_needed)
764
-
765
- # 5. Компенсируем магний
766
- mg_needed = self.target['Mg'] - self.actual['Mg']
767
- if mg_needed > 0:
768
- self._apply_fert("Сульфат магния", "Mg", mg_needed)
769
-
770
- # 6. Корректируем калий и серу
771
- self._balance_k_s()
772
-
773
- return self._prepare_results()
774
-
775
- def _apply_fert(self, name, element, needed_ppm):
776
- content = NUTRIENT_CONTENT[name][element]
777
- grams = (needed_ppm * self.volume) / (content * 1000)
778
-
779
- if name not in self.results:
780
- self.results[name] = {'граммы': 0, 'вклад': {}}
781
-
782
- self.results[name]['граммы'] += grams
783
-
784
- for el, val in NUTRIENT_CONTENT[name].items():
785
- added = grams * val * 1000 / self.volume
786
- self.actual[el] += added
787
- self.total_ec += added * EC_COEFF.get(el, 0)
788
 
789
- if el not in self.results[name]['вклад']:
790
- self.results[name]['вклад'][el] = 0
791
- self.results[name]['вклад'][el] += added
792
-
793
- def _balance_k_s(self):
794
- k_deficit = self.target['K'] - self.actual['K']
795
- s_deficit = self.target['S'] - self.actual['S']
796
-
797
- # Выбираем удобрение в зависимости от дефицита
798
- if k_deficit > 0 and s_deficit > 0:
799
- # Используем K2SO4 для одновременного восполнения
800
- self._apply_fert("Калий сернокислый", "K", min(k_deficit, s_deficit*0.44874/0.18401))
801
- elif k_deficit > 0:
802
- self._apply_fert("Калий азотнокислый", "K", k_deficit)
803
-
804
- def _prepare_results(self):
805
- # Расчет дефицитов
806
- deficits = {k: self.target[k] - self.actual[k]
807
- for k in self.target if abs(self.target[k] - self.actual[k]) > 0.1}
808
-
809
- return {
810
- "actual_profile": self.actual,
811
- "fertilizers": self._format_fertilizers(),
812
- "total_ec": round(self.total_ec, 2),
813
- "total_ppm": round(sum(self.actual.values()), 2),
814
- "deficits": deficits
815
- }
816
-
817
- def _format_fertilizers(self):
818
- formatted = {}
819
- for name, data in self.results.items():
820
- formatted[name] = {
821
- 'граммы': round(data['граммы'], 3),
822
- 'миллиграммы': int(data['граммы'] * 1000),
823
- 'вклад в EC': round(sum(v * EC_COEFF.get(k, 0)
824
- for k, v in data['вклад'].items()), 3),
825
- 'добавит': [f"{k}: {round(v, 1)} ppm" for k, v in data['вклад'].items()]
826
  }
827
- return formatted
828
-
829
- def print_report(self):
830
- print("\n" + "="*60)
831
- print("ПРОФИЛЬ ПИТАТЕЛЬНОГО РАСТВОРА (ИТОГО):")
832
- print("="*60)
833
- print(tabulate([[k, round(v, 1)] for k, v in self.actual.items()],
834
- headers=["Элемент", "ppm"]))
835
-
836
- print("\n" + "="*60)
837
- print(f"РАСЧЕТ ДЛЯ {self.volume} ЛИТРОВ")
838
- print("="*60)
839
- print(f"Общая концентрация: {round(sum(self.actual.values()), 1)} ppm")
840
- print(f"EC: {round(self.total_ec, 2)} mS/cm")
841
-
842
- print("\nРЕКОМЕНДУЕМЫЕ УДОБРЕНИЯ:")
843
- fert_table = []
844
- for name, data in self._format_fertilizers().items():
845
- fert_table.append([
846
- name,
847
- data['граммы'],
848
- data['миллиграммы'],
849
- data['вклад в EC'],
850
- "\n".join(data['добавит'])
851
- ])
852
- print(tabulate(fert_table,
853
- headers=["Удобрение", "Граммы", "Миллиграммы", "EC", "Добавит"]))
854
-
855
- if self._prepare_results()['deficits']:
856
- print("\nОСТАТОЧНЫЙ ДЕФИЦИТ:")
857
- for el, val in self._prepare_results()['deficits'].items():
858
- print(f" {el}: {round(val, 1)} ppm")
859
- else:
860
- print("\nВсе элементы покрыты полностью")
861
 
862
- # Пример использования
863
- if __name__ == "__main__":
864
- calc = NutrientCalculator(VOLUME_LITERS)
865
- results = calc.calculate()
866
- calc.print_report()
 
 
 
 
 
 
 
 
867
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
868
 
869
 
870
 
 
693
 
694
 
695
 
 
696
  import numpy as np
697
+ from tabulate import tabulate
698
 
699
  # Глобальные параметры
700
+ TOTAL_NITROGEN = 120.0 # Общее количество азота
701
+ NO3_RATIO = 8.0 # Соотношение NO3:NH4
702
+ NH4_RATIO = 1.0 # Соотношение NH4:NO3
703
+ VOLUME_LITERS = 100 # Объем раствора
704
 
 
705
  BASE_PROFILE = {
706
+ "P": 50, # Фосфор
707
+ "K": 210, # Калий
708
+ "Mg": 120, # Магний (высокий уровень)
709
+ "Ca": 150, # Кальций
710
+ "S": 50, # Сера
711
+ "N (NO3-)": 0, # Рассчитывается автоматически
712
+ "N (NH4+)": 0 # Рассчитывается автоматически
713
  }
714
 
715
+ NUTRIENT_CONTENT_IN_FERTILIZERS = {
 
716
  "Кальциевая селитра": {"N (NO3-)": 0.11863, "Ca": 0.16972},
717
  "Калий азотнокислый": {"N (NO3-)": 0.136, "K": 0.382},
718
  "Калий сернокислый": {"K": 0.44874, "S": 0.18401},
719
  "Аммоний азотнокислый": {"N (NO3-)": 0.17499, "N (NH4+)": 0.17499},
720
  "Сульфат магния": {"Mg": 0.09861, "S": 0.13010},
721
  "Монофосфат калия": {"P": 0.218, "K": 0.275},
722
+ "Сульфат кальция": {"Ca": 0.23, "S": 0.186},
723
+ "Кольцевая селитра": {"N (NO3-)": 0.15, "Ca": 0.20} # Новое удобрение
724
  }
725
 
726
+ EC_COEFFICIENTS = {
727
+ 'P': 0.0012, 'K': 0.0018, 'Mg': 0.0015,
728
+ 'Ca': 0.0016, 'S': 0.0014,
 
729
  'N (NO3-)': 0.0017, 'N (NH4+)': 0.0019
730
  }
731
 
732
+ nutrients_stencil = [
733
+ "N (NO3-)", "N (NH4+)", "P", "K", "Mg", "Ca", "S"
734
+ ]
735
+
736
+
737
+ class Composition:
738
+ def __init__(self, name='', vector=None):
739
+ self.name = name
740
+ if vector is None:
741
+ self.vector = np.zeros(len(nutrients_stencil))
742
+ else:
743
+ if len(vector) != len(nutrients_stencil):
744
+ raise ValueError(f"Vector length ({len(vector)}) does not match nutrients stencil length ({len(nutrients_stencil)}).")
745
+ self.vector = np.array(vector)
746
+
747
+ @classmethod
748
+ def from_dict(cls, composition_dict):
749
+ if not composition_dict:
750
+ raise ValueError("Empty composition dictionary provided.")
751
+ name, nutrients_dict = tuple(composition_dict.items())[0]
752
+ vector = np.zeros(len(nutrients_stencil))
753
+ for i, nutrient in enumerate(nutrients_stencil):
754
+ if nutrient in nutrients_dict:
755
+ vector[i] = nutrients_dict[nutrient]
756
+ return cls(name, vector)
757
+
758
+ def __add__(self, other):
759
+ if not isinstance(other, Composition):
760
+ raise TypeError("Can only add Composition objects.")
761
+ name = f'{self.name} + {other.name}'
762
+ vector = self.vector + other.vector
763
+ return Composition(name, vector)
764
+
765
+ def __repr__(self):
766
+ return self.table()
767
+
768
+ def table(self, sparse=True, ref=None, tablefmt='simple'):
769
+ description = f'Composition: {self.name}'
770
+ nutrients = np.array(nutrients_stencil)
771
+ vector = self.vector
772
+ if ref is not None:
773
+ if not isinstance(ref, Composition):
774
+ raise TypeError("Reference must be a Composition object.")
775
+ vector_ref = ref.vector
776
+ else:
777
+ vector_ref = np.zeros(len(nutrients_stencil))
778
+ if sparse:
779
+ mask_nonzero = (vector != 0) | (vector_ref != 0)
780
+ nutrients = nutrients[mask_nonzero]
781
+ vector = vector[mask_nonzero]
782
+ vector_ref = vector_ref[mask_nonzero]
783
+ table_dict = {
784
+ 'Nutrient': nutrients,
785
+ 'Ratio': vector,
786
+ 'Amount mg/kg': 10**6 * vector,
787
+ }
788
+ if ref is not None:
789
+ description += f'\nReference: {ref.name}'
790
+ table_dict['Diff mg/kg'] = 10**6 * (vector - vector_ref)
791
+ table = tabulate(table_dict, headers='keys', tablefmt=tablefmt)
792
+ return '\n\n'.join((description, table))
793
+
794
+
795
  class NutrientCalculator:
796
+ def __init__(self, volume_liters=1.0):
797
+ self.volume = volume_liters
798
+ self.target_profile = BASE_PROFILE.copy()
799
+ self.fertilizers = {
800
+ name: Composition.from_dict({name: content})
801
+ for name, content in NUTRIENT_CONTENT_IN_FERTILIZERS.items()
802
+ }
803
  self.total_ec = 0.0
804
+ self.best_solution = None
805
+ self.min_difference = float('inf')
806
+ self.max_recursion_depth = 1000
807
+ self.current_depth = 0
808
+
809
+ # Расчёт азота
810
+ total_parts = NO3_RATIO + NH4_RATIO
811
+ self.target_profile['N (NO3-)'] = TOTAL_NITROGEN * (NO3_RATIO / total_parts)
812
+ self.target_profile['N (NH4+)'] = TOTAL_NITROGEN * (NH4_RATIO / total_parts)
813
+
814
+ # Целевой профиль как объект Composition
815
+ self.target_composition = Composition('Target Profile', list(self.target_profile.values()))
816
+
817
  def calculate(self):
818
+ try:
819
+ self.actual_profile = {k: 0.0 for k in self.target_profile}
820
+ self.results = {}
821
+ self.current_depth = 0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
822
 
823
+ if self._backtrack_search():
824
+ print("Оптимальная комбинация найдена!")
825
+ return self.best_solution
826
+ else:
827
+ print("Идеальное решение не найдено. Возвращаю лучшее найденное решение.")
828
+ return self.best_solution or {"error": "Не удалось найти подходящую комбинацию"}
829
+
830
+ except Exception as e:
831
+ print(f"Ошибка при расчёте: {str(e)}")
832
+ raise
833
+
834
+ def _backtrack_search(self, fertilizer_index=0, step=1.0):
835
+ self.current_depth += 1
836
+ if self.current_depth > self.max_recursion_depth:
837
+ return False
838
+
839
+ # Текущий профиль как объект Composition
840
+ current_composition = Composition('Current Profile', list(self.actual_profile.values()))
841
+ current_diff = self._calculate_difference(current_composition)
842
+
843
+ print(f"\nТекущая разница: {current_diff:.2f}")
844
+ print("Текущий профиль:")
845
+ print(current_composition)
846
+
847
+ if current_diff < self.min_difference:
848
+ self.min_difference = current_diff
849
+ self.best_solution = {
850
+ "results": self._copy_results(),
851
+ "actual_profile": self.actual_profile.copy(),
852
+ "total_ec": self.total_ec,
853
+ "difference": current_diff
 
 
 
 
 
 
854
  }
855
+
856
+ if current_diff < 1.0: # Допустимая погрешность
857
+ return True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
858
 
859
+ # Пробуем добавлять удобрения с текущего индекса
860
+ for i in range(fertilizer_index, len(self.fertilizers)):
861
+ fert_name = list(self.fertilizers.keys())[i]
862
+ fert_composition = self.fertilizers[fert_name]
863
+
864
+ print(f"\nПроверяю удобрение: {fert_name}")
865
+
866
+ # Проверяем, можно ли применить удобрение
867
+ if not self._can_apply_fertilizer(fert_composition):
868
+ print(f"Удобрение {fert_name} не подходит (превышает целевые значения).")
869
+ continue
870
+
871
+ print(f"Добавляю удобрение: {fert_name}, количество: {step:.2f} г")
872
 
873
+ # Пробуем добавить удобрение с текущим шагом
874
+ self._apply_fertilizer(fert_name, step)
875
+
876
+ # Рекурсивно продолжаем поиск
877
+ if self._backtrack_search(i, step):
878
+ return True
879
+
880
+ # Если не получилось - откатываемся
881
+ print(f"Откатываю удобрение: {fert_name}, количество: {step:.2f} г")
882
+ self._remove_fertilizer(fert_name, step)
883
+
884
+ # Пробуем уменьшить шаг для более точного поиска
885
+ if step > 0.1 and current_diff > 5.0:
886
+ print(f"Уменьшаю шаг до {step / 2:.2f} г")
887
+ if self._backtrack_search(i, step / 2):
888
+ return True
889
+
890
+ return False
891
+
892
+ def _can_apply_fertilizer(self, fert_composition):
893
+ """Проверяет, можно ли применить удобрение без перебора"""
894
+ for element, content in zip(nutrients_stencil, fert_composition.vector):
895
+ added_ppm = (1 * content * 1000) / self.volume
896
+ if self.actual_profile[element] + added_ppm > self.target_profile[element] + 0.5: # Уменьшаем допустимую погрешность
897
+ return False
898
+ return True
899
+
900
+ def _apply_fertilizer(self, fert_name, amount):
901
+ """Добавляет указанное количество удобрения"""
902
+ fert_composition = self.fertilizers[fert_name]
903
+ scaled_composition = amount * fert_composition
904
+
905
+ if fert_name not in self.results:
906
+ self.results[fert_name] = {
907
+ 'граммы': 0.0,
908
+ 'миллиграммы': 0,
909
+ 'вклад в EC': 0.0
910
+ }
911
+
912
+ self.results[fert_name]['граммы'] += amount
913
+ self.results[fert_name]['миллиграммы'] += int(amount * 1000)
914
+
915
+ for i, nutrient in enumerate(nutrients_stencil):
916
+ added_ppm = scaled_composition.vector[i] * 1000 / self.volume
917
+ self.actual_profile[nutrient] += added_ppm
918
+
919
+ def _remove_fertilizer(self, fert_name, amount):
920
+ """Удаляет указанное количество удобрения"""
921
+ fert_composition = self.fertilizers[fert_name]
922
+ scaled_composition = amount * fert_composition
923
+
924
+ if fert_name in self.results:
925
+ self.results[fert_name]['граммы'] -= amount
926
+ self.results[fert_name]['миллиграммы'] -= int(amount * 1000)
927
+
928
+ for i, nutrient in enumerate(nutrients_stencil):
929
+ removed_ppm = scaled_composition.vector[i] * 1000 / self.volume
930
+ self.actual_profile[nutrient] -= removed_ppm
931
+
932
+ if self.results[fert_name]['граммы'] <= 0.001:
933
+ del self.results[fert_name]
934
+
935
+ def _calculate_difference(self, current_composition):
936
+ """Вычисляет общее отклонение от целевого профиля"""
937
+ diff_vector = self.target_composition.vector - current_composition.vector
938
+ return np.sum(np.abs(diff_vector))
939
+
940
+ def generate_report(self):
941
+ """Генерация отчета о питательном растворе"""
942
+ try:
943
+ actual_composition = Composition('Actual Profile', list(self.actual_profile.values()))
944
+ report = actual_composition.table(sparse=True, ref=self.target_composition)
945
+ return report
946
+ except Exception as e:
947
+ print(f"Ошибка при выводе отчёта: {str(e)}")
948
+ raise
949
+
950
+
951
+ if __name__ == "__main__":
952
+ try:
953
+ calculator = NutrientCalculator(volume_liters=VOLUME_LITERS)
954
+ solution = calculator.calculate()
955
+ if solution:
956
+ print(calculator.generate_report())
957
+ else:
958
+ print("Решение не найдено.")
959
+ except Exception as e:
960
+ print(f"Критическая ошибка: {str(e)}")
961
 
962
 
963