diff --git a/celavi/component.py b/celavi/component.py index edfe707..80a2192 100644 --- a/celavi/component.py +++ b/celavi/component.py @@ -187,7 +187,7 @@ def bol_process(self, env): # Identify manufacturing facility based on distance and, for secondary manuf # facilities, whether the facility has sufficient inventory to manufacture the # component - + # Locate the closest (by cost) manufacturing facilities _manuf_dict = self.context.cost_graph.find_upstream_neighbor( node_id = self.in_use_facility, @@ -257,16 +257,19 @@ def bol_process(self, env): self.manuf_facility = _fac break # ends the loop - # Increment manufacturing inventories count_inventory = self.context.count_facility_inventories[self.manuf_facility] mass_inventory = self.context.mass_facility_inventories[self.manuf_facility] + # Increment virgin manufacturing inventories ONLY + # (secondary manuf facilities already have inventory) if self.manuf_facility.split('_')[0] in self.virgin_manuf_facility_types: count_inventory.increment_quantity(self.kind, self.count, env.now) for material, mass in self.mass_tonnes.items(): mass_inventory.increment_quantity(material, mass, env.now) # Component waits to transition to in use + # This is done for only virgin facilities, because the inventory check on secondary facilities + # must be done in the same timestep as the component is manufactured yield env.timeout(lifespan) # Decrement manufacturing inventories @@ -281,6 +284,11 @@ def bol_process(self, env): for material, mass in self.mass_tonnes.items(): mass_inventory.increment_quantity(material, -mass, env.now) + # if the component is manufactured from a secondary facility, wait one more timestep + # this should bring the timing of the virgin and secondary supply chains to alignment + if self.manuf_facility.split('_')[0] not in self.virgin_manuf_facility_types: + env.timeout(lifespan) + # Increment and decrement intermediate manufacturing facilities # Identify pathway from manuf_facility to in_use_facility for _fac in nx.astar_path( @@ -448,6 +456,7 @@ def eol_process(self, env): yield env.timeout(lifespan) # Decrement the current facility inventory + # @NOTE Check this statement in case of "Inventory cannot go negative" warnings self.move_component_from(env, loc = location, amt = 1 - _loss) diff --git a/celavi/des.py b/celavi/des.py index c206914..f95eafa 100644 --- a/celavi/des.py +++ b/celavi/des.py @@ -363,7 +363,10 @@ def pylca_interface_process(self, env): annual_data_for_lci = [] window_last_timestep = env.now - window_first_timestep = env.now - self.timesteps_per_year + if self.timesteps_per_year > 1: + window_first_timestep = env.now - self.timesteps_per_year + else: + window_first_timestep = env.now year = int(floor(self.timesteps_to_years(env.now))) @@ -390,7 +393,10 @@ def pylca_interface_process(self, env): ) ) ) - problematic_value = sliced_info[mat][self.timesteps_per_year] + try: + problematic_value = sliced_info[mat][self.timesteps_per_year] + except KeyError: + problematic_value = 0 # A problematic value is when mass is reported in the last time step of a sliced dataframe # which belongs to the next year. Generally this happens only for manufacturing. diff --git a/celavi/inventory.py b/celavi/inventory.py index 55b7998..33e239f 100644 --- a/celavi/inventory.py +++ b/celavi/inventory.py @@ -128,14 +128,11 @@ def increment_quantity( # This logic should rarely if ever be used, as updates to component.bol_process and # component.eol_process should prevent inventories going negative print(f""" -{self.facility_type}_{self.facility_id} at {timestep=}: Inventory cannot go negative. -{quantity} required, {self.component_materials[item_name]} -in stock\nDecreasing quantity to {self.component_materials[item_name]} +{self.facility_type}_{self.facility_id} at {timestep=}: Inventory cannot go negative. {quantity} required, {self.component_materials[item_name]} in stock\nDecreasing quantity to {self.component_materials[item_name]} """, flush=True ) - quantity = -1.0 * self.component_materials[item_name] - + quantity = -1.0 * self.component_materials[item_name] # Place this transaction in the history timestep = int(timestep) diff --git a/celavi/pylca_celavi/des_interface.py b/celavi/pylca_celavi/des_interface.py index 0c10c17..11d859b 100644 --- a/celavi/pylca_celavi/des_interface.py +++ b/celavi/pylca_celavi/des_interface.py @@ -161,6 +161,7 @@ def __init__( if stop_flag == 1: sys.exit(0) + # Set up Brightway environment variable os.environ["BRIGHTWAY2_DIR"] = str(self.brightway_dir) if self.verbose: @@ -207,7 +208,7 @@ def lca_performance_improvement( result_df Cached impacts for matching rows. """ - + try: # Read cache without header, assign expected columns cache_df = pd.read_csv( @@ -227,9 +228,9 @@ def lca_performance_improvement( how='outer', indicator=True, ) + missing_df = merged[merged['_merge'] == 'left_only'].drop(columns=['_merge']) result_df = merged[merged['_merge'] == 'both'].drop(columns=['_merge']) - if result_df.empty: logger.warning( "Shortcut LCA cache missing entry for %s, %s, %d", @@ -246,8 +247,8 @@ def lca_performance_improvement( result_df['flow quantity'] * result_df['cached_value'] ) cols = [ - 'lcia', 'value', 'unit', 'year', 'method', - 'facility_id', 'stage', 'material', 'route_id', 'state' + 'lcia', 'unit', 'year', 'method', + 'facility_id', 'stage', 'material', 'route_id', 'state','value' ] return missing_df, result_df[cols] diff --git a/celavi/scenario.py b/celavi/scenario.py index 66d1b26..e3981b6 100644 --- a/celavi/scenario.py +++ b/celavi/scenario.py @@ -641,8 +641,6 @@ def postprocess(self): f, mode="a", header=f.tell() == 0, index=False, lineterminator="\n" ) - diagnostic_viz_counts.generate_plots() - # Plot the levels of the mass inventories diagnostic_viz_mass = DiagnosticViz( facility_inventories=self.context.mass_facility_inventories, @@ -660,8 +658,6 @@ def postprocess(self): diagnostic_viz_mass.gather_and_melt_cumulative_histories() ) - diagnostic_viz_mass.generate_plots() - # Postprocess and save CostGraph outputs self.netw.save_costgraph_outputs() #Save lca shortcut file @@ -859,6 +855,9 @@ def postprocess(self): central_summary.to_csv( f, index=False, mode="a", header=f.tell() == 0, lineterminator="\n" ) + + diagnostic_viz_counts.generate_plots() + diagnostic_viz_mass.generate_plots() def calculate_circularity_metrics(self, mass):