diff --git a/RUFAS/biophysical/manure/manure_manager.py b/RUFAS/biophysical/manure/manure_manager.py index c511ddbbdb..384705b7de 100644 --- a/RUFAS/biophysical/manure/manure_manager.py +++ b/RUFAS/biophysical/manure/manure_manager.py @@ -3,7 +3,7 @@ from dataclasses import replace from typing import Any -from RUFAS.biophysical.manure.field_manure_supplier import FieldManureSupplier +from RUFAS.data_structures.field_manure_supplier import FieldManureSupplier from RUFAS.biophysical.manure.handler.handler import Handler from RUFAS.biophysical.manure.manure_nutrient_manager import ManureNutrientManager from RUFAS.biophysical.manure.processor import Processor diff --git a/RUFAS/biophysical/manure/field_manure_supplier.py b/RUFAS/data_structures/field_manure_supplier.py similarity index 100% rename from RUFAS/biophysical/manure/field_manure_supplier.py rename to RUFAS/data_structures/field_manure_supplier.py diff --git a/RUFAS/simulation_engine.py b/RUFAS/simulation_engine.py index f1b7235ee0..ff4aa354e1 100644 --- a/RUFAS/simulation_engine.py +++ b/RUFAS/simulation_engine.py @@ -13,6 +13,7 @@ from RUFAS.data_structures.animal_to_manure_connection import ManureStream from RUFAS.data_structures.crop_soil_to_feed_storage_connection import HarvestedCrop from RUFAS.data_structures.feed_storage_to_animal_connection import NutrientStandard +from RUFAS.data_structures.field_manure_supplier import FieldManureSupplier from RUFAS.data_structures.manure_to_crop_soil_connection import ManureEventNutrientRequestResults from RUFAS.input_manager import InputManager from RUFAS.output_manager import OutputManager @@ -36,6 +37,8 @@ class SimulationType(Enum): FULL_FARM = "full_farm" FIELD_AND_FEED = "field_and_feed" + FIELD_ONLY = "field_only" + FIELD_WITH_STORAGE = "field_with_storage" @property def simulate_animals(self) -> bool: @@ -47,6 +50,8 @@ def _non_animal_simulation_types(cls) -> set["SimulationType"]: """Return the set of simulation types that do not simulate animals.""" return { cls.FIELD_AND_FEED, + cls.FIELD_ONLY, + cls.FIELD_WITH_STORAGE, } @classmethod @@ -110,6 +115,8 @@ def __init__(self, simulation_type: SimulationType) -> None: self._simulation_type_to_daily_simulation_function = { SimulationType.FULL_FARM: self._execute_full_farm_daily_simulation, SimulationType.FIELD_AND_FEED: self._execute_field_and_feed_daily_simulation, + simulation_type.FIELD_ONLY: self._execute_field_only_simulation, + simulation_type.FIELD_WITH_STORAGE: self._execute_field_with_storage_simulation, } self._initialize_simulation() @@ -211,7 +218,7 @@ def _execute_full_farm_daily_simulation(self) -> None: 7. Advance simulation date """ - daily_harvested_crops = self._execute_daily_field_operations() + daily_harvested_crops = self._execute_daily_field_with_storage_operations() harvest_schedule = self._build_harvest_schedule(daily_harvested_crops) self._execute_feed_planning(harvest_schedule) @@ -226,6 +233,38 @@ def _execute_full_farm_daily_simulation(self) -> None: self._advance_time() + def _execute_field_with_storage_simulation(self) -> None: + """ + Executes the daily simulation routines for a farm with only the field and storage modules. + + Daily Field With Storage Simulation Process: + 1. Field operations (manure applications, harvesting) + 2. Record keeping (time, weather, purchased feeds fed emissions) + 3. Advance simulation date + + """ + self._execute_daily_field_with_storage_operations() + + self._report_daily_records() + + self._advance_time() + + def _execute_field_only_simulation(self) -> None: + """ + Executes the daily simulation routines for a field only modules. + + Daily Field Process: + 1. Field operations without sending the crops to the feed manager + 2. Record keeping (time, weather, purchased feeds fed emissions) + 3. Advance simulation date + + """ + self._execute_daily_field_only_operations() + + self._report_daily_records() + + self._advance_time() + def _execute_field_and_feed_daily_simulation(self) -> None: """ Executes the daily simulation routines for a farm with only the field and feed modules. @@ -239,7 +278,7 @@ def _execute_field_and_feed_daily_simulation(self) -> None: 5. Advance simulation date """ - daily_harvested_crops = self._execute_daily_field_operations() + daily_harvested_crops = self._execute_daily_field_with_storage_operations() harvest_schedule = self._build_harvest_schedule(daily_harvested_crops) self._execute_feed_planning(harvest_schedule) @@ -250,13 +289,20 @@ def _execute_field_and_feed_daily_simulation(self) -> None: self._advance_time() - def _execute_daily_field_operations(self) -> list[HarvestedCrop]: - """Handles daily field operations including manure applications and crop harvesting/receiving.""" + def _execute_daily_field_only_operations(self) -> list[HarvestedCrop]: + """Handles daily field operations including manure applications.""" manure_applications: list[ManureEventNutrientRequestResults] = self.generate_daily_manure_applications() + harvested_crops: list[HarvestedCrop] = self.field_manager.daily_update_routine( self.weather, self.time, manure_applications ) + return harvested_crops + + def _execute_daily_field_with_storage_operations(self) -> list[HarvestedCrop]: + """Handles daily field operations including manure applications and crop harvesting/receiving.""" + harvested_crops: list[HarvestedCrop] = self._execute_daily_field_only_operations() + for crop in harvested_crops: self.feed_manager.receive_crop(crop, self.time.simulation_day) @@ -279,9 +325,7 @@ def generate_daily_manure_applications(self) -> list[ManureEventNutrientRequestR manure_request = manure_event_request.nutrient_request manure_request_results = None if manure_request is not None: - manure_request_results = self.manure_manager.request_nutrients( - manure_request, self.simulate_animals, self.time - ) + manure_request_results = FieldManureSupplier.request_nutrients(manure_request) manure_applications.append(ManureEventNutrientRequestResults(field_name, event, manure_request_results)) return manure_applications diff --git a/changelog.md b/changelog.md index b1500f223d..d357116e8a 100644 --- a/changelog.md +++ b/changelog.md @@ -60,10 +60,10 @@ v1.0.0 - [2921](https://github.com/RuminantFarmSystems/RuFaS/pull/2921) - [minor change] [OutputManager][NoInputChange] [NoOutputChange] Override incorrect fill type for complex data structure data padding. - [2924](https://github.com/RuminantFarmSystems/RuFaS/pull/2924) - [minor change] [NoInputChange] [NoOutputChange] Updated advance purchase allowance to prevent excessive warnings for example run. - [2929](https://github.com/RuminantFarmSystems/RuFaS/pull/2929) - [minor change] [GraphGenerator] [NoInputChange] [NoOutputChange] Sanitizes non-numerical data sent to graph generator to allow graphing to occur despite. +- [2932](https://github.com/RuminantFarmSystems/RuFaS/pull/2932) - [minor change] [SimulationEngine] [NoInputChange] [NoOutputChange] Enabled Field only and Field with storage simulation. - [2925](https://github.com/RuminantFarmSystems/RuFaS/pull/2925) - [minor change] [NoInputChange] [NoOutputChange] Fix the `graph_and_report` option in report_generation.py. - [2907](https://github.com/RuminantFarmSystems/RuFaS/pull/2907) - [minor change] [NoInputChange] [OutputChange] Fix the FarmGrownFeed Emissions unit issue. The mirror issue of [Fix FarmGrownFeed Emissions Unit on test #2908](https://github.com/RuminantFarmSystems/MASM/pull/2908) to update `dev`. - ### v1.0.0 - [2081](https://github.com/RuminantFarmSystems/RuFaS/pull/2081) - [minor change] [Crop & Soil] Break down the `_setup_field()` function in `FieldManager`. diff --git a/input/data/config/example_field_only_config.json b/input/data/config/example_field_only_config.json new file mode 100644 index 0000000000..a0816edbc8 --- /dev/null +++ b/input/data/config/example_field_only_config.json @@ -0,0 +1,10 @@ +{ + "start_date": "2013:1", + "end_date": "2019:365", + "random_seed": 42, + "set_seed": true, + "simulation_type": "field_only", + "nutrient_standard": "NASEM", + "FIPS_county_code": 55025, + "include_detailed_values": false +} \ No newline at end of file diff --git a/input/data/config/example_field_with_storage_config.json b/input/data/config/example_field_with_storage_config.json new file mode 100644 index 0000000000..6418cf2de1 --- /dev/null +++ b/input/data/config/example_field_with_storage_config.json @@ -0,0 +1,10 @@ +{ + "start_date": "2013:1", + "end_date": "2019:365", + "random_seed": 42, + "set_seed": true, + "simulation_type": "field_with_storage", + "nutrient_standard": "NASEM", + "FIPS_county_code": 55025, + "include_detailed_values": false +} \ No newline at end of file diff --git a/input/data/tasks/example_field_only_task.json b/input/data/tasks/example_field_only_task.json new file mode 100644 index 0000000000..1af56a30fc --- /dev/null +++ b/input/data/tasks/example_field_only_task.json @@ -0,0 +1,40 @@ +{ + "parallel_workers": 4, + "input_data_csv_export_path": "output/saved_input_data/.", + "input_data_csv_import_path": ".", + "export_input_data_to_csv": false, + "tasks": [ + { + "task_type": "SIMULATION_SINGLE_RUN", + "metadata_file_path": "input/metadata/example_field_only_metadata.json", + "output_prefix": "field_only", + "log_verbosity": "errors", + "random_seed": 42, + "SA_load_balancing_stop": 1, + "SA_load_balancing_start": 0, + "sampler_n": 2, + "skip_values": 0, + "sampler": "sobol", + "multi_run_counts": 4, + "maximum_memory_usage_percent": 80, + "maximum_memory_usage": 0, + "save_chunk_threshold_call_count": 0, + "chunkification": false, + "suppress_log_files": false, + "logs_directory": "output/logs/.", + "properties_file_path": "input/metadata/properties/default.json", + "comparison_properties_file_path": "input/metadata/properties/default.json", + "variable_name_style": "basic", + "exclude_info_maps": false, + "init_herd": false, + "save_animals": false, + "save_animals_directory": "output/.", + "filters_directory": "output/output_filters/.", + "csv_output_directory": "output/CSVs/.", + "json_output_directory": "output/JSONs/.", + "report_directory": "output/reports/.", + "graphics_directory": "output/graphics/.", + "cross_validation_file_paths": [] + } + ] +} \ No newline at end of file diff --git a/input/data/tasks/example_field_with_storage_task.json b/input/data/tasks/example_field_with_storage_task.json new file mode 100644 index 0000000000..f647c79824 --- /dev/null +++ b/input/data/tasks/example_field_with_storage_task.json @@ -0,0 +1,40 @@ +{ + "parallel_workers": 4, + "input_data_csv_export_path": "output/saved_input_data/.", + "input_data_csv_import_path": ".", + "export_input_data_to_csv": false, + "tasks": [ + { + "task_type": "SIMULATION_SINGLE_RUN", + "metadata_file_path": "input/metadata/example_field_with_storage_metadata.json", + "output_prefix": "field_only", + "log_verbosity": "errors", + "random_seed": 42, + "SA_load_balancing_stop": 1, + "SA_load_balancing_start": 0, + "sampler_n": 2, + "skip_values": 0, + "sampler": "sobol", + "multi_run_counts": 4, + "maximum_memory_usage_percent": 80, + "maximum_memory_usage": 0, + "save_chunk_threshold_call_count": 0, + "chunkification": false, + "suppress_log_files": false, + "logs_directory": "output/logs/.", + "properties_file_path": "input/metadata/properties/default.json", + "comparison_properties_file_path": "input/metadata/properties/default.json", + "variable_name_style": "basic", + "exclude_info_maps": false, + "init_herd": false, + "save_animals": false, + "save_animals_directory": "output/.", + "filters_directory": "output/output_filters/.", + "csv_output_directory": "output/CSVs/.", + "json_output_directory": "output/JSONs/.", + "report_directory": "output/reports/.", + "graphics_directory": "output/graphics/.", + "cross_validation_file_paths": [] + } + ] +} \ No newline at end of file diff --git a/input/metadata/example_field_only_metadata.json b/input/metadata/example_field_only_metadata.json new file mode 100644 index 0000000000..85eabda25f --- /dev/null +++ b/input/metadata/example_field_only_metadata.json @@ -0,0 +1,254 @@ +{ + "files": { + "config": { + "title": "Config Data", + "description": "Configuration file for general simulation parameters.", + "path": "input/data/config/example_field_only_config.json", + "type": "json", + "properties": "config_properties" + }, + "animal": { + "title": "Animal data", + "description": "Input data and configuration information for animal management decisions, herd attributes, and housing properties.", + "path": "input/data/animal/example_freestall_animal.json", + "type": "json", + "properties": "animal_properties" + }, + "animal_population": { + "title": "Animal population data", + "description": "Animal objects that herd initialization draws from during the simulation. A similar file can be generated using the -I and -s command line arguments simultaneously.", + "path": "input/data/animal/animal_population.json", + "type": "json", + "properties": "animal_population_properties" + }, + "animal_net_merit": { + "title": "Animal Net Merit data", + "description": "The net merit value for cows.", + "path": "input/data/animal/animal_genetics/NetMerit_HO.csv", + "type": "csv", + "properties": "animal_net_merit_properties" + }, + "animal_top_listing_semen": { + "title": "Animal Top Listing Semen data", + "description": "The top listing semen value for new born calves.", + "path": "input/data/animal/animal_genetics/TopListingSemen_HO.csv", + "type": "csv", + "properties": "animal_top_listing_semen_properties" + }, + "lactation": { + "title": "Lactation curve adjustment values", + "description": "Values for the adjustment of the three wood parameters based on farm specific data", + "path": "input/data/animal/lactation_curve_adjustment_inputs.json", + "type": "json", + "properties": "lactation_properties" + }, + "economy": { + "title": "Economy data", + "description": "Energy prices used in the EEE module.", + "path": "input/data/EEE/default_costs.csv", + "type": "csv", + "properties": "economic_properties" + }, + "emission": { + "title": "Emissions data", + "description": "General emission values used in the EEE module.", + "path": "input/data/EEE/default_emissions.csv", + "type": "csv", + "properties": "emissions_properties" + }, + "purchased_feeds_emissions": { + "title": "Emissions from purchased feeds", + "description": "Purchased feeds emission values used in the EEE module. Missing data interpolated following script in helpful_scripts/emissions_interpolation.", + "path": "input/data/EEE/full_feeds_emissions_July2024_interpolated_regional_average.csv", + "type": "csv", + "properties": "feed_emissions_properties" + }, + "purchased_feed_land_use_change_emissions": { + "title": "Land Use Change emissions from purchased feeds", + "description": "Purchased feeds land use change emission values used in the EEE module. Missing data interpolated following script in helpful_scripts/emissions_interpolation.", + "path": "input/data/EEE/full_feeds_land_use_change_emissions_July2024_interpolated_regional_average.csv", + "type": "csv", + "properties": "feed_emissions_properties" + }, + "feed": { + "title": "Feed data", + "description": "Feeds available for each animal combination, purchased feeds and their prices, feed storage options, and user-defined ration percentages and associated parameters.", + "path": "input/data/feed/example_Midwest_feed.json", + "type": "json", + "properties": "feed_properties" + }, + "NRC_Comp": { + "title": "NRC Comp data", + "description": "Nutritional information for each feed, following NRC (2001) guidelines.", + "path": "input/data/feed/NRC_comp.csv", + "type": "csv", + "properties": "NRC_Comp_properties" + }, + "NASEM_Comp": { + "title": "NASEM Comp data", + "description": "Nutritional information for each feed, following NASEM (2021) guidelines.", + "path": "input/data/feed/NASEM_Comp_with_TDN_urea.csv", + "type": "csv", + "properties": "NASEM_Comp_properties" + }, + "manure_management": { + "title": "Manure Processor Configurations", + "description": "Configurations of manure processors (handlers, separators, digesters, and storages) used in Manure module.", + "path": "input/data/manure/example_freestall_processor_configs.json", + "type": "json", + "properties": "manure_management_properties" + }, + "manure_processor_connection": { + "title": "Manure Processor Connections", + "description": "The connection configs for all manure processors.", + "path": "input/data/manure/example_freestall_processor_connections.json", + "type": "json", + "properties": "manure_processor_connection_properties" + }, + "crop_configurations": { + "title": "Crop Configurations", + "description": "Configurations for how crops grow and are managed.", + "path": "input/data/crop_configurations/default_crop_configs.json", + "type": "json", + "properties": "crop_configuration_properties" + }, + "field_1": { + "title": "Field specification", + "description": "Field characteristics and references to field management specifications.", + "path": "input/data/field/example_small_field_corn_alf_silage.json", + "type": "json", + "properties": "field_properties" + }, + "soil_1": { + "title": "Soil data", + "description": "Characteristic soil information, including composition, slope, and layer details.", + "path": "input/data/soil/example_soil.json", + "type": "json", + "properties": "soil_profile_properties" + }, + "Corn-Alf-Silage": { + "title": "Crop data", + "description": "Crop selection and detailed rotation schedules.", + "path": "input/data/crop/example_alf_corn_silage_rotation.json", + "type": "json", + "properties": "crop_schedule_properties" + }, + "fertilizer_schedule_1": { + "title": "Fertilizer schedule.", + "description": "Fertilizer types available, and their application schedule.", + "path": "input/data/fertilizer_schedule/example_sm_alf_corn_fertilizer.json", + "type": "json", + "properties": "fertilizer_schedule_properties" + }, + "manure_schedule_1": { + "title": "Manure schedule.", + "description": "Specifies manure applications to a field.", + "path": "input/data/manure_schedule/example_sm_corn_alf_manure_schedule.json", + "type": "json", + "properties": "manure_schedule_properties" + }, + "tillage_schedule_1": { + "title": "Tillage schedule.", + "description": "Schedule of tillage applications for a field.", + "path": "input/data/tillage_schedule/no_till.json", + "type": "json", + "properties": "tillage_schedule_properties" + }, + "field_2": { + "title": "Field specification", + "description": "Field characteristics and references to field management specifications.", + "path": "input/data/field/example_small_field_corn_grain_alf_hay.json", + "type": "json", + "properties": "field_properties" + }, + "soil_2": { + "title": "Soil data", + "description": "Characteristic soil information, including composition, slope, and layer details.", + "path": "input/data/soil/example_soil_2.json", + "type": "json", + "properties": "soil_profile_properties" + }, + "CornGrain-AlfHay": { + "title": "Crop data", + "description": "Crop selection and detailed rotation schedules.", + "path": "input/data/crop/example_alf_hay_corn_grain_rotation.json", + "type": "json", + "properties": "crop_schedule_properties" + }, + "fertilizer_schedule_2": { + "title": "Fertilizer schedule.", + "description": "Fertilizer types available, and their application schedule.", + "path": "input/data/fertilizer_schedule/example_sm_alf_corn_fertilizer.json", + "type": "json", + "properties": "fertilizer_schedule_properties" + }, + "manure_schedule_2": { + "title": "Manure schedule.", + "description": "Specifies manure applications to a field.", + "path": "input/data/manure_schedule/example_sm_corn_alf_manure_schedule.json", + "type": "json", + "properties": "manure_schedule_properties" + }, + "tillage_schedule_2": { + "title": "Tillage schedule.", + "description": "Schedule of tillage applications for a field.", + "path": "input/data/tillage_schedule/no_till.json", + "type": "json", + "properties": "tillage_schedule_properties" + }, + "weather": { + "title": "Weather data", + "description": "Weather data used during the simulation, including date, precipitation, temperature, etc.", + "path": "input/data/weather/example_temperate_weather.csv", + "type": "csv", + "properties": "weather_properties" + }, + "user_feeds": { + "title": "User Feed", + "description": "Summary of feeds available for use in the simulation, including their RuFaS ID, short descriptors, and which nutrient composition file they are included in.", + "path": "input/data/feed/user_feeds.csv", + "type": "csv", + "properties": "user_feeds_properties" + }, + "tractor_dataset": { + "title": "Tractor Dataset", + "description": "Details agricultural machinery operations for various crops, including tractor size, operations (planting, mowing, collection, etc.), implement details, operational parameters (depth, width, mass), and throughput metrics, spanning different crop types and soil management practices.", + "path": "input/data/EEE/tractor_dataset.csv", + "type": "csv", + "properties": "tractor_dataset_properties" + }, + "EEE_constants": { + "title": "EEE Constants", + "description": "The constants that are used in EEE module.", + "path": "input/data/EEE/constants.json", + "type": "json", + "properties": "EEE_constants_properties" + }, + "feed_management": { + "title": "Feed Management", + "description": "Configurations for feed storage units.", + "path": "input/data/feed_management/example_feed_storage_configs.json", + "type": "json", + "properties": "feed_storage_configurations" + }, + "feed_storage_configurations": { + "title": "Feed Management", + "description": "Configurations for feed storage units.", + "path": "input/data/feed_management/example_feed_storage_configs.json", + "type": "json", + "properties": "feed_storage_configurations" + }, + "feed_storage_instances": { + "title": "Feed Storages", + "description": "Names of feed storage configs used in the simulation.", + "path": "input/data/feed_management/example_freestall_feed_storages.json", + "type": "json", + "properties": "feed_storage_instances" + } + }, + "runtime_metadata": { + "EEE_econ": { + "path": "input/metadata/EEE/econ_metadata.json" + } + } +} \ No newline at end of file diff --git a/input/metadata/example_field_with_storage_metadata.json b/input/metadata/example_field_with_storage_metadata.json new file mode 100644 index 0000000000..b7fbc05876 --- /dev/null +++ b/input/metadata/example_field_with_storage_metadata.json @@ -0,0 +1,254 @@ +{ + "files": { + "config": { + "title": "Config Data", + "description": "Configuration file for general simulation parameters.", + "path": "input/data/config/example_field_with_storage_config.json", + "type": "json", + "properties": "config_properties" + }, + "animal": { + "title": "Animal data", + "description": "Input data and configuration information for animal management decisions, herd attributes, and housing properties.", + "path": "input/data/animal/example_freestall_animal.json", + "type": "json", + "properties": "animal_properties" + }, + "animal_population": { + "title": "Animal population data", + "description": "Animal objects that herd initialization draws from during the simulation. A similar file can be generated using the -I and -s command line arguments simultaneously.", + "path": "input/data/animal/animal_population.json", + "type": "json", + "properties": "animal_population_properties" + }, + "animal_net_merit": { + "title": "Animal Net Merit data", + "description": "The net merit value for cows.", + "path": "input/data/animal/animal_genetics/NetMerit_HO.csv", + "type": "csv", + "properties": "animal_net_merit_properties" + }, + "animal_top_listing_semen": { + "title": "Animal Top Listing Semen data", + "description": "The top listing semen value for new born calves.", + "path": "input/data/animal/animal_genetics/TopListingSemen_HO.csv", + "type": "csv", + "properties": "animal_top_listing_semen_properties" + }, + "lactation": { + "title": "Lactation curve adjustment values", + "description": "Values for the adjustment of the three wood parameters based on farm specific data", + "path": "input/data/animal/lactation_curve_adjustment_inputs.json", + "type": "json", + "properties": "lactation_properties" + }, + "economy": { + "title": "Economy data", + "description": "Energy prices used in the EEE module.", + "path": "input/data/EEE/default_costs.csv", + "type": "csv", + "properties": "economic_properties" + }, + "emission": { + "title": "Emissions data", + "description": "General emission values used in the EEE module.", + "path": "input/data/EEE/default_emissions.csv", + "type": "csv", + "properties": "emissions_properties" + }, + "purchased_feeds_emissions": { + "title": "Emissions from purchased feeds", + "description": "Purchased feeds emission values used in the EEE module. Missing data interpolated following script in helpful_scripts/emissions_interpolation.", + "path": "input/data/EEE/full_feeds_emissions_July2024_interpolated_regional_average.csv", + "type": "csv", + "properties": "feed_emissions_properties" + }, + "purchased_feed_land_use_change_emissions": { + "title": "Land Use Change emissions from purchased feeds", + "description": "Purchased feeds land use change emission values used in the EEE module. Missing data interpolated following script in helpful_scripts/emissions_interpolation.", + "path": "input/data/EEE/full_feeds_land_use_change_emissions_July2024_interpolated_regional_average.csv", + "type": "csv", + "properties": "feed_emissions_properties" + }, + "feed": { + "title": "Feed data", + "description": "Feeds available for each animal combination, purchased feeds and their prices, feed storage options, and user-defined ration percentages and associated parameters.", + "path": "input/data/feed/example_Midwest_feed.json", + "type": "json", + "properties": "feed_properties" + }, + "NRC_Comp": { + "title": "NRC Comp data", + "description": "Nutritional information for each feed, following NRC (2001) guidelines.", + "path": "input/data/feed/NRC_comp.csv", + "type": "csv", + "properties": "NRC_Comp_properties" + }, + "NASEM_Comp": { + "title": "NASEM Comp data", + "description": "Nutritional information for each feed, following NASEM (2021) guidelines.", + "path": "input/data/feed/NASEM_Comp_with_TDN_urea.csv", + "type": "csv", + "properties": "NASEM_Comp_properties" + }, + "manure_management": { + "title": "Manure Processor Configurations", + "description": "Configurations of manure processors (handlers, separators, digesters, and storages) used in Manure module.", + "path": "input/data/manure/example_freestall_processor_configs.json", + "type": "json", + "properties": "manure_management_properties" + }, + "manure_processor_connection": { + "title": "Manure Processor Connections", + "description": "The connection configs for all manure processors.", + "path": "input/data/manure/example_freestall_processor_connections.json", + "type": "json", + "properties": "manure_processor_connection_properties" + }, + "crop_configurations": { + "title": "Crop Configurations", + "description": "Configurations for how crops grow and are managed.", + "path": "input/data/crop_configurations/default_crop_configs.json", + "type": "json", + "properties": "crop_configuration_properties" + }, + "field_1": { + "title": "Field specification", + "description": "Field characteristics and references to field management specifications.", + "path": "input/data/field/example_small_field_corn_alf_silage.json", + "type": "json", + "properties": "field_properties" + }, + "soil_1": { + "title": "Soil data", + "description": "Characteristic soil information, including composition, slope, and layer details.", + "path": "input/data/soil/example_soil.json", + "type": "json", + "properties": "soil_profile_properties" + }, + "Corn-Alf-Silage": { + "title": "Crop data", + "description": "Crop selection and detailed rotation schedules.", + "path": "input/data/crop/example_alf_corn_silage_rotation.json", + "type": "json", + "properties": "crop_schedule_properties" + }, + "fertilizer_schedule_1": { + "title": "Fertilizer schedule.", + "description": "Fertilizer types available, and their application schedule.", + "path": "input/data/fertilizer_schedule/example_sm_alf_corn_fertilizer.json", + "type": "json", + "properties": "fertilizer_schedule_properties" + }, + "manure_schedule_1": { + "title": "Manure schedule.", + "description": "Specifies manure applications to a field.", + "path": "input/data/manure_schedule/example_sm_corn_alf_manure_schedule.json", + "type": "json", + "properties": "manure_schedule_properties" + }, + "tillage_schedule_1": { + "title": "Tillage schedule.", + "description": "Schedule of tillage applications for a field.", + "path": "input/data/tillage_schedule/no_till.json", + "type": "json", + "properties": "tillage_schedule_properties" + }, + "field_2": { + "title": "Field specification", + "description": "Field characteristics and references to field management specifications.", + "path": "input/data/field/example_small_field_corn_grain_alf_hay.json", + "type": "json", + "properties": "field_properties" + }, + "soil_2": { + "title": "Soil data", + "description": "Characteristic soil information, including composition, slope, and layer details.", + "path": "input/data/soil/example_soil_2.json", + "type": "json", + "properties": "soil_profile_properties" + }, + "CornGrain-AlfHay": { + "title": "Crop data", + "description": "Crop selection and detailed rotation schedules.", + "path": "input/data/crop/example_alf_hay_corn_grain_rotation.json", + "type": "json", + "properties": "crop_schedule_properties" + }, + "fertilizer_schedule_2": { + "title": "Fertilizer schedule.", + "description": "Fertilizer types available, and their application schedule.", + "path": "input/data/fertilizer_schedule/example_sm_alf_corn_fertilizer.json", + "type": "json", + "properties": "fertilizer_schedule_properties" + }, + "manure_schedule_2": { + "title": "Manure schedule.", + "description": "Specifies manure applications to a field.", + "path": "input/data/manure_schedule/example_sm_corn_alf_manure_schedule.json", + "type": "json", + "properties": "manure_schedule_properties" + }, + "tillage_schedule_2": { + "title": "Tillage schedule.", + "description": "Schedule of tillage applications for a field.", + "path": "input/data/tillage_schedule/no_till.json", + "type": "json", + "properties": "tillage_schedule_properties" + }, + "weather": { + "title": "Weather data", + "description": "Weather data used during the simulation, including date, precipitation, temperature, etc.", + "path": "input/data/weather/example_temperate_weather.csv", + "type": "csv", + "properties": "weather_properties" + }, + "user_feeds": { + "title": "User Feed", + "description": "Summary of feeds available for use in the simulation, including their RuFaS ID, short descriptors, and which nutrient composition file they are included in.", + "path": "input/data/feed/user_feeds.csv", + "type": "csv", + "properties": "user_feeds_properties" + }, + "tractor_dataset": { + "title": "Tractor Dataset", + "description": "Details agricultural machinery operations for various crops, including tractor size, operations (planting, mowing, collection, etc.), implement details, operational parameters (depth, width, mass), and throughput metrics, spanning different crop types and soil management practices.", + "path": "input/data/EEE/tractor_dataset.csv", + "type": "csv", + "properties": "tractor_dataset_properties" + }, + "EEE_constants": { + "title": "EEE Constants", + "description": "The constants that are used in EEE module.", + "path": "input/data/EEE/constants.json", + "type": "json", + "properties": "EEE_constants_properties" + }, + "feed_management": { + "title": "Feed Management", + "description": "Configurations for feed storage units.", + "path": "input/data/feed_management/example_feed_storage_configs.json", + "type": "json", + "properties": "feed_storage_configurations" + }, + "feed_storage_configurations": { + "title": "Feed Management", + "description": "Configurations for feed storage units.", + "path": "input/data/feed_management/example_feed_storage_configs.json", + "type": "json", + "properties": "feed_storage_configurations" + }, + "feed_storage_instances": { + "title": "Feed Storages", + "description": "Names of feed storage configs used in the simulation.", + "path": "input/data/feed_management/example_freestall_feed_storages.json", + "type": "json", + "properties": "feed_storage_instances" + } + }, + "runtime_metadata": { + "EEE_econ": { + "path": "input/metadata/EEE/econ_metadata.json" + } + } +} \ No newline at end of file diff --git a/input/metadata/properties/default.json b/input/metadata/properties/default.json index 01e7294c9c..4eaf50e8c7 100644 --- a/input/metadata/properties/default.json +++ b/input/metadata/properties/default.json @@ -17,7 +17,7 @@ "type": "string", "description": "The type of daily simulation to run. Will determine daily simulation function in SimulationEngine.", "default": "full_farm", - "pattern": "^(full_farm|field_and_feed)$" + "pattern": "^(full_farm|field_and_feed|field_only|field_with_storage)$" }, "nutrient_standard": { "type": "string", diff --git a/tests/test_biophysical/test_manure/test_field_manure_supplier.py b/tests/test_biophysical/test_manure/test_field_manure_supplier.py index 302e45ed21..3a41b8ccac 100644 --- a/tests/test_biophysical/test_manure/test_field_manure_supplier.py +++ b/tests/test_biophysical/test_manure/test_field_manure_supplier.py @@ -1,6 +1,6 @@ import pytest -from RUFAS.biophysical.manure.field_manure_supplier import FieldManureSupplier +from RUFAS.data_structures.field_manure_supplier import FieldManureSupplier from RUFAS.data_structures.manure_to_crop_soil_connection import NutrientRequest, NutrientRequestResults from RUFAS.data_structures.manure_types import ManureType diff --git a/tests/test_biophysical/test_manure/test_manure_manager/test_manure_manager.py b/tests/test_biophysical/test_manure/test_manure_manager/test_manure_manager.py index 041987a735..61190a9c98 100644 --- a/tests/test_biophysical/test_manure/test_manure_manager/test_manure_manager.py +++ b/tests/test_biophysical/test_manure/test_manure_manager/test_manure_manager.py @@ -6,7 +6,7 @@ from pytest_mock import MockerFixture, MockFixture from RUFAS.biophysical.manure.digester.digester import Digester -from RUFAS.biophysical.manure.field_manure_supplier import FieldManureSupplier +from RUFAS.data_structures.field_manure_supplier import FieldManureSupplier from RUFAS.biophysical.manure.manure_manager import STORAGE_CLASS_TO_TYPE, ManureManager from RUFAS.biophysical.manure.manure_nutrient_manager import ManureNutrientManager from RUFAS.biophysical.manure.processor import Processor diff --git a/tests/test_simulation_engine.py b/tests/test_simulation_engine.py index 01f710d2b1..9fa2c78a4b 100644 --- a/tests/test_simulation_engine.py +++ b/tests/test_simulation_engine.py @@ -41,6 +41,8 @@ def test_simulation_type_enum_values() -> None: """Unit test for SimulationType enum values.""" assert SimulationType.FULL_FARM.value == "full_farm" assert SimulationType.FIELD_AND_FEED.value == "field_and_feed" + assert SimulationType.FIELD_ONLY.value == "field_only" + assert SimulationType.FIELD_WITH_STORAGE.value == "field_with_storage" @pytest.mark.parametrize( @@ -48,6 +50,8 @@ def test_simulation_type_enum_values() -> None: [ (SimulationType.FULL_FARM, True), (SimulationType.FIELD_AND_FEED, False), + (SimulationType.FIELD_ONLY, False), + (SimulationType.FIELD_WITH_STORAGE, False), ], ) def test_simulate_animals( @@ -60,7 +64,11 @@ def test_simulate_animals( def test_non_animal_simulation_types() -> None: """Unit test for SimulationType._non_animal_simulation_types.""" - assert SimulationType._non_animal_simulation_types() == {SimulationType.FIELD_AND_FEED} + assert SimulationType._non_animal_simulation_types() == { + SimulationType.FIELD_AND_FEED, + SimulationType.FIELD_ONLY, + SimulationType.FIELD_WITH_STORAGE, + } @pytest.mark.parametrize( @@ -68,6 +76,8 @@ def test_non_animal_simulation_types() -> None: [ ("full_farm", SimulationType.FULL_FARM), ("field_and_feed", SimulationType.FIELD_AND_FEED), + ("field_only", SimulationType.FIELD_ONLY), + ("field_with_storage", SimulationType.FIELD_WITH_STORAGE), ], ) def test_get_simulation_type_valid( @@ -81,10 +91,11 @@ def test_get_simulation_type_valid( def test_get_simulation_type_invalid() -> None: """Unit test for SimulationType.get_simulation_type with an invalid value.""" invalid_simulation_type = "not_a_real_simulation" + valid_simulation_types = ", ".join(simulation_type.value for simulation_type in SimulationType) with pytest.raises( ValueError, - match=("Unknown simulation type: not_a_real_simulation. " "Expected one of: full_farm, field_and_feed."), + match=(f"Unknown simulation type: {invalid_simulation_type}. " f"Expected one of: {valid_simulation_types}."), ): SimulationType.get_simulation_type(invalid_simulation_type) @@ -191,7 +202,7 @@ def test_execute_full_farm_daily_simulation( mock_execute_daily_field_operations = mocker.patch.object( simulation_engine, - "_execute_daily_field_operations", + "_execute_daily_field_with_storage_operations", return_value=daily_harvested_crops, ) mock_build_harvest_schedule = mocker.patch.object( @@ -243,7 +254,7 @@ def test_execute_field_and_feed_daily_simulation( parent.attach_mock( mocker.patch.object( simulation_engine, - "_execute_daily_field_operations", + "_execute_daily_field_with_storage_operations", return_value=daily_harvested_crops, ), "execute_daily_field_operations", @@ -327,7 +338,7 @@ def test_execute_daily_field_operations( simulation_engine.feed_manager = MagicMock() # Act - result = simulation_engine._execute_daily_field_operations() + result = simulation_engine._execute_daily_field_with_storage_operations() # Assert mock_generate_daily_manure_applications.assert_called_once_with() @@ -371,7 +382,7 @@ def test_execute_daily_field_operations_no_harvested_crops( simulation_engine.feed_manager = MagicMock() # Act - result = simulation_engine._execute_daily_field_operations() + result = simulation_engine._execute_daily_field_with_storage_operations() # Assert mock_generate_daily_manure_applications.assert_called_once_with() @@ -384,6 +395,105 @@ def test_execute_daily_field_operations_no_harvested_crops( assert result == harvested_crops +def test_execute_daily_field_only_operations( + simulation_engine: SimulationEngine, + mocker: MockerFixture, +) -> None: + """ + Unit test for function _execute_daily_field_only_operations + in file RUFAS/simulation_engine.py + """ + manure_applications = [MagicMock(), MagicMock()] + harvested_crops = [MagicMock(), MagicMock()] + + mock_generate_daily_manure_applications = mocker.patch.object( + simulation_engine, + "generate_daily_manure_applications", + return_value=manure_applications, + ) + + simulation_engine.weather = MagicMock() + simulation_engine.time = MagicMock() + simulation_engine.field_manager = MagicMock() + simulation_engine.field_manager.daily_update_routine.return_value = harvested_crops + simulation_engine.feed_manager = MagicMock() + + result = simulation_engine._execute_daily_field_only_operations() + + mock_generate_daily_manure_applications.assert_called_once_with() + simulation_engine.field_manager.daily_update_routine.assert_called_once_with( + simulation_engine.weather, + simulation_engine.time, + manure_applications, + ) + simulation_engine.feed_manager.receive_crop.assert_not_called() + assert result == harvested_crops + + +def test_execute_field_only_simulation( + simulation_engine: SimulationEngine, + mocker: MockerFixture, +) -> None: + """ + Unit test for function _execute_field_only_simulation + in file RUFAS/simulation_engine.py + """ + parent = MagicMock() + + parent.attach_mock( + mocker.patch.object(simulation_engine, "_execute_daily_field_only_operations"), + "execute_daily_field_only_operations", + ) + parent.attach_mock( + mocker.patch.object(simulation_engine, "_report_daily_records"), + "report_daily_records", + ) + parent.attach_mock( + mocker.patch.object(simulation_engine, "_advance_time"), + "advance_time", + ) + + simulation_engine._execute_field_only_simulation() + + assert parent.mock_calls == [ + call.execute_daily_field_only_operations(), + call.report_daily_records(), + call.advance_time(), + ] + + +def test_execute_field_with_storage_simulation( + simulation_engine: SimulationEngine, + mocker: MockerFixture, +) -> None: + """ + Unit test for function _execute_field_with_storage_simulation + in file RUFAS/simulation_engine.py + """ + parent = MagicMock() + + parent.attach_mock( + mocker.patch.object(simulation_engine, "_execute_daily_field_with_storage_operations"), + "execute_daily_field_with_storage_operations", + ) + parent.attach_mock( + mocker.patch.object(simulation_engine, "_report_daily_records"), + "report_daily_records", + ) + parent.attach_mock( + mocker.patch.object(simulation_engine, "_advance_time"), + "advance_time", + ) + + simulation_engine._execute_field_with_storage_simulation() + + assert parent.mock_calls == [ + call.execute_daily_field_with_storage_operations(), + call.report_daily_records(), + call.advance_time(), + ] + + def test_build_harvest_schedule_no_feed_recalculation( simulation_engine: SimulationEngine, mocker: MockerFixture,