DmitrMakeev commited on
Commit
d3ccad5
·
verified ·
1 Parent(s): 89174bc

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +39 -211
app.py CHANGED
@@ -712,10 +712,11 @@ def nutri_call():
712
 
713
  from tabulate import tabulate
714
  import numpy as np
 
715
 
716
  # Глобальные параметры
717
  TOTAL_NITROGEN = 120.0 # Общее количество азота
718
- NO3_RATIO = 8.25 # Соотношение NO3:NH4
719
  NH4_RATIO = 1.0 # Соотношение NH4:NO3
720
  VOLUME_LITERS = 100 # Объем раствора
721
 
@@ -743,244 +744,71 @@ EC_COEFFICIENTS = {
743
  'N (NO3-)': 0.0017, 'N (NH4+)': 0.0019
744
  }
745
 
746
- nutrients_stencil = [
747
- "N (NO3-)", "N (NH4+)", "P", "K", "Mg", "Ca", "S"
748
- ]
749
-
750
-
751
- class Composition:
752
- def __init__(self, name='', vector=None):
753
- self.name = name
754
- if vector is None:
755
- self.vector = np.zeros(len(nutrients_stencil))
756
- else:
757
- if len(vector) != len(nutrients_stencil):
758
- raise ValueError(f"Vector length ({len(vector)}) does not match nutrients stencil length ({len(nutrients_stencil)}).")
759
- self.vector = np.array(vector)
760
-
761
- @classmethod
762
- def from_dict(cls, composition_dict):
763
- if not composition_dict:
764
- raise ValueError("Empty composition dictionary provided.")
765
- name, nutrients_dict = tuple(composition_dict.items())[0]
766
- vector = np.zeros(len(nutrients_stencil))
767
- for i, nutrient in enumerate(nutrients_stencil):
768
- if nutrient in nutrients_dict:
769
- vector[i] = nutrients_dict[nutrient]
770
- return cls(name, vector)
771
-
772
- def __add__(self, other):
773
- if not isinstance(other, Composition):
774
- raise TypeError("Can only add Composition objects.")
775
- name = f'{self.name} + {other.name}'
776
- vector = self.vector + other.vector
777
- return Composition(name, vector)
778
-
779
- def table(self, sparse=True, ref=None, tablefmt='simple'):
780
- description = f'Composition: {self.name}'
781
- nutrients = np.array(nutrients_stencil)
782
- vector = self.vector
783
- if ref is not None:
784
- if not isinstance(ref, Composition):
785
- raise TypeError("Reference must be a Composition object.")
786
- vector_ref = ref.vector
787
- else:
788
- vector_ref = np.zeros(len(nutrients_stencil))
789
- if sparse:
790
- mask_nonzero = (vector != 0) | (vector_ref != 0)
791
- nutrients = nutrients[mask_nonzero]
792
- vector = vector[mask_nonzero]
793
- vector_ref = vector_ref[mask_nonzero]
794
- table_dict = {
795
- 'Nutrient': nutrients,
796
- 'Ratio': vector,
797
- 'Amount mg/kg': 10**6 * vector,
798
- }
799
- if ref is not None:
800
- description += f'\nReference: {ref.name}'
801
- table_dict['Diff mg/kg'] = 10**6 * (vector - vector_ref)
802
- table = tabulate(table_dict, headers='keys', tablefmt=tablefmt)
803
- return '\n\n'.join((description, table))
804
 
805
 
806
  class NutrientCalculator:
807
- def __init__(self, volume_liters=1.0):
808
- self.volume = volume_liters
809
  self.target_profile = BASE_PROFILE.copy()
810
- self.fertilizers = {
811
- name: Composition.from_dict({name: content})
812
- for name, content in NUTRIENT_CONTENT_IN_FERTILIZERS.items()
813
- }
814
- self.total_ec = 0.0
815
- self.best_solution = None
816
- self.min_difference = float('inf')
817
- self.max_recursion_depth = 5000 # Увеличиваем глубину поиска
818
- self.current_depth = 0
819
 
820
  # Расчёт азота
821
  total_parts = NO3_RATIO + NH4_RATIO
822
  self.target_profile['N (NO3-)'] = TOTAL_NITROGEN * (NO3_RATIO / total_parts)
823
  self.target_profile['N (NH4+)'] = TOTAL_NITROGEN * (NH4_RATIO / total_parts)
824
 
825
- # Целевой профиль как объект Composition
826
- self.target_composition = Composition('Target Profile', list(self.target_profile.values()))
827
 
828
  def calculate(self):
829
  try:
830
- self.actual_profile = {k: 0.0 for k in self.target_profile}
831
- self.results = {}
832
- self.current_depth = 0
833
-
834
- if self._backtrack_search():
835
- print("Оптимальная комбинация найдена!")
836
- else:
837
- print("Идеальное решение не найдено. Возвращаю лучшее найденное решение.")
838
-
839
- # Попытка точного добора после основного подбора
840
- self._post_optimize()
841
-
842
- return self.best_solution or {"error": "Не удалось найти подходящую комбинацию"}
 
 
 
 
 
 
 
 
843
 
844
  except Exception as e:
845
  print(f"Ошибка при расчёте: {str(e)}")
846
  raise
847
 
848
- def _backtrack_search(self, fertilizer_index=0, step=1.0):
849
- self.current_depth += 1
850
- if self.current_depth > self.max_recursion_depth:
851
- return False
852
-
853
- # Текущий профиль как объект Composition
854
- current_composition = Composition('Current Profile', list(self.actual_profile.values()))
855
- current_diff = self._calculate_difference(current_composition)
856
-
857
- if current_diff < self.min_difference:
858
- self.min_difference = current_diff
859
- self.best_solution = {
860
- "results": self._copy_results(),
861
- "actual_profile": self.actual_profile.copy(),
862
- "total_ec": self.total_ec,
863
- "difference": current_diff
864
- }
865
-
866
- if current_diff < 1.0: # Допустимая погрешность
867
- return True
868
-
869
- # Пробуем добавлять удобрения с текущего индекса
870
- for i in range(fertilizer_index, len(self.fertilizers)):
871
- fert_name = list(self.fertilizers.keys())[i]
872
- fert_composition = self.fertilizers[fert_name]
873
-
874
- # Проверяем, можно ли применить удобрение
875
- if not self._can_apply_fertilizer(fert_composition):
876
- continue
877
-
878
- # Пробуем добавить удобрение с текущим шагом
879
- self._apply_fertilizer(fert_name, step)
880
-
881
- # Рекурсивно продолжаем поиск
882
- if self._backtrack_search(i, step):
883
- return True
884
-
885
- # Если не получилось - откатываемся
886
- self._remove_fertilizer(fert_name, step)
887
-
888
- # Уменьшаем шаг для более точного поиска
889
- if step > 0.1:
890
- if self._backtrack_search(i, step / 2):
891
- return True
892
-
893
- return False
894
-
895
- def _can_apply_fertilizer(self, fert_composition):
896
- """Проверяет, можно ли применить удобрение без перебора"""
897
- for element, content in zip(nutrients_stencil, fert_composition.vector):
898
- added_ppm = (1 * content * 1000) / self.volume
899
- if self.actual_profile[element] + added_ppm > self.target_profile[element] * 1.03: # Разрешаем перерасход на 3%
900
- return False
901
- return True
902
-
903
- def _apply_fertilizer(self, fert_name, amount):
904
- """Добавляет указанное количество удобрения"""
905
- fert_composition = self.fertilizers[fert_name]
906
- scaled_composition = Composition(fert_composition.name, fert_composition.vector * amount)
907
-
908
- if fert_name not in self.results:
909
- self.results[fert_name] = {
910
- 'граммы': 0.0,
911
- 'миллиграммы': 0,
912
- 'вклад в EC': 0.0
913
- }
914
-
915
- self.results[fert_name]['граммы'] += amount
916
- self.results[fert_name]['миллиграммы'] += int(amount * 1000)
917
-
918
- for i, nutrient in enumerate(nutrients_stencil):
919
- added_ppm = scaled_composition.vector[i] * 1000 / self.volume
920
- self.actual_profile[nutrient] += added_ppm
921
-
922
- def _remove_fertilizer(self, fert_name, amount):
923
- """Удаляет указанное количество удобрения"""
924
- fert_composition = self.fertilizers[fert_name]
925
- scaled_composition = Composition(fert_composition.name, fert_composition.vector * amount)
926
-
927
- if fert_name in self.results:
928
- self.results[fert_name]['граммы'] -= amount
929
- self.results[fert_name]['миллиграммы'] -= int(amount * 1000)
930
-
931
- for i, nutrient in enumerate(nutrients_stencil):
932
- removed_ppm = scaled_composition.vector[i] * 1000 / self.volume
933
- self.actual_profile[nutrient] -= removed_ppm
934
-
935
- if self.results[fert_name]['граммы'] <= 0.001:
936
- del self.results[fert_name]
937
-
938
- def _calculate_difference(self, current_composition):
939
- """Вычисляет общее отклонение от целевого профиля с учетом весов"""
940
- diff_vector = self.target_composition.vector - current_composition.vector
941
- weights = np.array([1.5 if el in ['K', 'S', 'Mg'] else 1.0 for el in nutrients_stencil])
942
- return np.sum(np.abs(diff_vector) * weights)
943
-
944
- def _copy_results(self):
945
- """Создаёт глубокую копию результатов"""
946
- return {
947
- fert_name: {
948
- 'граммы': data['граммы'],
949
- 'миллиграммы': data['миллиграммы'],
950
- 'вклад в EC': data['вклад в EC']
951
- }
952
- for fert_name, data in self.results.items()
953
- }
954
-
955
- def _post_optimize(self):
956
- """Попытка точного добора после основного подбора"""
957
- for fert_name, fert in self.fertilizers.items():
958
- for i, nutrient in enumerate(nutrients_stencil):
959
- deficit = self.target_profile[nutrient] - self.actual_profile[nutrient]
960
- if deficit > 2.0 and fert.vector[i] > 0: # Если дефицит больше 2 ppm
961
- small_amount = deficit * self.volume / (fert.vector[i] * 1000)
962
- self._apply_fertilizer(fert_name, min(small_amount, 2.0)) # Не больше 2 г
963
-
964
- def generate_report(self):
965
  """Генерация отчета с фактическими данными"""
966
  try:
967
- # Добавляем информацию о добавленных удобрениях
968
  fertilizer_report = "Фактически добавленные удобрения (в граммах):\n"
969
- for fert_name, data in self.results.items():
970
- grams = data['граммы']
971
- if grams > 0: # Выводим только те удобрения, которые были добавлены
972
- fertilizer_report += f" - {fert_name}: {grams:.2f} г\n"
973
 
974
  return fertilizer_report
975
  except Exception as e:
976
  print(f"Ошибка при выводе отчёта: {str(e)}")
977
  raise
 
 
978
  if __name__ == "__main__":
979
  try:
980
- calculator = NutrientCalculator(volume_liters=VOLUME_LITERS)
981
- solution = calculator.calculate()
982
- if solution:
983
- print(calculator.generate_report())
984
  else:
985
  print("Решение не найдено.")
986
  except Exception as e:
 
712
 
713
  from tabulate import tabulate
714
  import numpy as np
715
+ import numpy as np
716
 
717
  # Глобальные параметры
718
  TOTAL_NITROGEN = 120.0 # Общее количество азота
719
+ NO3_RATIO = 8.25 # Соотношение NO3:NH4
720
  NH4_RATIO = 1.0 # Соотношение NH4:NO3
721
  VOLUME_LITERS = 100 # Объем раствора
722
 
 
744
  'N (NO3-)': 0.0017, 'N (NH4+)': 0.0019
745
  }
746
 
747
+ nutrients_stencil = list(BASE_PROFILE.keys())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
748
 
749
 
750
  class NutrientCalculator:
751
+ def __init__(self):
752
+ self.volume = VOLUME_LITERS
753
  self.target_profile = BASE_PROFILE.copy()
754
+ self.fertilizers = NUTRIENT_CONTENT_IN_FERTILIZERS
755
+ self.nutrients_stencil = nutrients_stencil
 
 
 
 
 
 
 
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.target_vector = np.array([self.target_profile[nutrient] for nutrient in self.nutrients_stencil])
764
 
765
  def calculate(self):
766
  try:
767
+ # П��строение матрицы A (содержание элементов в удобрениях)
768
+ fertilizer_names = list(self.fertilizers.keys())
769
+ A = np.zeros((len(self.nutrients_stencil), len(fertilizer_names)))
770
+ for i, nutrient in enumerate(self.nutrients_stencil):
771
+ for j, fert_name in enumerate(fertilizer_names):
772
+ if nutrient in self.fertilizers[fert_name]:
773
+ A[i, j] = self.fertilizers[fert_name][nutrient]
774
+
775
+ # Формирование вектора B (целевые значения в ppm)
776
+ B = self.target_vector * self.volume / 1000 # Перевод ppm в граммы
777
+
778
+ # Решение системы уравнений A @ X = B
779
+ X, residuals, rank, s = np.linalg.lstsq(A, B, rcond=None)
780
+
781
+ # Формирование результата
782
+ results = {}
783
+ for i, fert_name in enumerate(fertilizer_names):
784
+ if X[i] > 0:
785
+ results[fert_name] = X[i]
786
+
787
+ return results
788
 
789
  except Exception as e:
790
  print(f"Ошибка при расчёте: {str(e)}")
791
  raise
792
 
793
+ def generate_report(self, results):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
794
  """Генерация отчета с фактическими данными"""
795
  try:
 
796
  fertilizer_report = "Фактически добавленные удобрения (в граммах):\n"
797
+ for fert_name, grams in results.items():
798
+ fertilizer_report += f" - {fert_name}: {grams:.2f} г\n"
 
 
799
 
800
  return fertilizer_report
801
  except Exception as e:
802
  print(f"Ошибка при выводе отчёта: {str(e)}")
803
  raise
804
+
805
+
806
  if __name__ == "__main__":
807
  try:
808
+ calculator = NutrientCalculator()
809
+ results = calculator.calculate()
810
+ if results:
811
+ print(calculator.generate_report(results))
812
  else:
813
  print("Решение не найдено.")
814
  except Exception as e: