@@ -2386,7 +2386,7 @@ def get_emissions(n, region, _energy_totals):
2386
2386
.multiply (t2Mt )
2387
2387
)
2388
2388
2389
- var ["Emissions|CO2" ] = co2_emissions .sum () - co2_atmosphere_withdrawal .sum ()
2389
+ var ["Emissions|CO2|Model " ] = co2_emissions .sum () - co2_atmosphere_withdrawal .sum ()
2390
2390
2391
2391
co2_storage = (
2392
2392
n .statistics .supply (bus_carrier = "co2 stored" , ** kwargs )
@@ -2400,19 +2400,94 @@ def get_emissions(n, region, _energy_totals):
2400
2400
assert co2_storage .get ("co2 stored" , 0 ) < 1.0
2401
2401
co2_storage .drop ("co2 stored" , inplace = True , errors = "ignore" )
2402
2402
2403
+ try :
2404
+ total_ccs = (
2405
+ n .statistics .supply (bus_carrier = "co2 sequestered" , ** kwargs )
2406
+ .filter (like = region )
2407
+ .get ("Link" )
2408
+ .groupby ("carrier" )
2409
+ .sum ()
2410
+ .multiply (t2Mt )
2411
+ .sum ()
2412
+ )
2413
+ except AttributeError : # no sequestration in 2020 -> NoneType
2414
+ total_ccs = 0.0
2415
+
2416
+ # CCU is regarded as emissions
2417
+ ccs_fraction = total_ccs / co2_storage .sum ()
2418
+ ccu_fraction = 1 - ccs_fraction
2419
+
2420
+ common_index_emitters = co2_emissions .index .intersection (co2_storage .index )
2421
+
2422
+ co2_emissions .loc [common_index_emitters ] += co2_storage .loc [
2423
+ common_index_emitters
2424
+ ].multiply (ccu_fraction )
2425
+
2426
+ common_index_withdrawals = co2_atmosphere_withdrawal .index .intersection (
2427
+ co2_storage .index
2428
+ )
2429
+
2430
+ co2_atmosphere_withdrawal .loc [common_index_withdrawals ] -= co2_storage .loc [
2431
+ common_index_withdrawals
2432
+ ].multiply (ccu_fraction )
2433
+
2434
+ assert isclose (
2435
+ co2_emissions .sum () - co2_atmosphere_withdrawal .sum (),
2436
+ var ["Emissions|CO2|Model" ] + co2_storage .sum () * ccu_fraction ,
2437
+ )
2438
+
2439
+ # Now repeat the same for the CHP emissions
2440
+
2403
2441
CHP_emissions = (
2404
- n .statistics .supply (bus_carrier = "co2" , ** kwargs )
2405
- .filter (like = region )
2406
- .filter (like = "CHP" )
2407
- .multiply (t2Mt )
2442
+ (
2443
+ n .statistics .supply (bus_carrier = "co2" , ** kwargs )
2444
+ .filter (like = region )
2445
+ .filter (like = "CHP" )
2446
+ .multiply (t2Mt )
2447
+ )
2448
+ .groupby (["name" , "carrier" ])
2449
+ .sum ()
2408
2450
)
2409
2451
2410
- # exclude waste CHPs because they are accounted separately
2411
- CHP_emissions = CHP_emissions [
2412
- ~ CHP_emissions .index .get_level_values ("carrier" ).str .contains ("waste" )
2413
- ]
2452
+ CHP_atmosphere_withdrawal = (
2453
+ (
2454
+ n .statistics .withdrawal (bus_carrier = "co2" , ** kwargs )
2455
+ .filter (like = region )
2456
+ .filter (like = "CHP" )
2457
+ .multiply (t2Mt )
2458
+ )
2459
+ .groupby (["name" , "carrier" ])
2460
+ .sum ()
2461
+ )
2462
+
2463
+ CHP_storage = (
2464
+ (
2465
+ n .statistics .supply (bus_carrier = "co2 stored" , ** kwargs )
2466
+ .filter (like = region )
2467
+ .filter (like = "CHP" )
2468
+ .multiply (t2Mt )
2469
+ )
2470
+ .groupby (["name" , "carrier" ])
2471
+ .sum ()
2472
+ )
2473
+
2474
+ # CCU is regarded as emissions
2475
+
2476
+ common_index_emitters = CHP_emissions .index .intersection (CHP_storage .index )
2477
+
2478
+ CHP_emissions .loc [common_index_emitters ] += CHP_storage .loc [
2479
+ common_index_emitters
2480
+ ].multiply (ccu_fraction )
2481
+
2482
+ common_index_withdrawals = CHP_atmosphere_withdrawal .index .intersection (
2483
+ CHP_storage .index
2484
+ )
2414
2485
2415
- ## Account for carbon neutral fuels (e-fuels, biogas, ...)
2486
+ CHP_atmosphere_withdrawal .loc [common_index_withdrawals ] -= CHP_storage .loc [
2487
+ common_index_withdrawals
2488
+ ].multiply (ccu_fraction )
2489
+
2490
+ ## E-fuels are assumed to be carbon neutral
2416
2491
2417
2492
oil_techs = [
2418
2493
"HVC to air" ,
@@ -2428,14 +2503,15 @@ def get_emissions(n, region, _energy_totals):
2428
2503
"urban central oil CHP" ,
2429
2504
]
2430
2505
2506
+ # multiply by fossil fraction to disregard e-fuel emissions
2507
+
2431
2508
oil_fossil_fraction = _get_oil_fossil_fraction (n , region )
2432
- # Assuming that efuel emissions are generated at the production site
2433
- var ["Emissions|CO2|Energy|Production|From Liquids" ] = co2_emissions .loc [
2509
+
2510
+ # This variable is not in the database, but it might be useful for double checking the totals
2511
+ var ["Emissions|CO2|Efuels|Liquids" ] = co2_emissions .loc [
2434
2512
co2_emissions .index .isin (oil_techs )
2435
2513
].sum () * (1 - oil_fossil_fraction )
2436
2514
2437
- # Fossil fuel emissions are generated where they get burned
2438
-
2439
2515
co2_emissions .loc [co2_emissions .index .isin (oil_techs )] *= oil_fossil_fraction
2440
2516
2441
2517
CHP_emissions .loc [
@@ -2457,17 +2533,32 @@ def get_emissions(n, region, _energy_totals):
2457
2533
2458
2534
gas_fractions = _get_gas_fractions (n , region )
2459
2535
2460
- var ["Emissions|CO2|Energy|Production|From Gases" ] = co2_emissions .loc [
2536
+ var ["Emissions|CO2|Efuels| Gases" ] = co2_emissions .loc [
2461
2537
co2_emissions .index .isin (gas_techs )
2462
2538
].sum () * (1 - gas_fractions ["Natural Gas" ])
2539
+
2463
2540
co2_emissions .loc [co2_emissions .index .isin (gas_techs )] *= gas_fractions [
2464
2541
"Natural Gas"
2465
2542
]
2466
2543
CHP_emissions .loc [
2467
2544
CHP_emissions .index .get_level_values ("carrier" ).isin (gas_techs )
2468
2545
] *= gas_fractions ["Natural Gas" ]
2469
2546
2470
- # TODO Methanol
2547
+ # TODO Methanol?
2548
+
2549
+ # Emissions in DE are:
2550
+
2551
+ var ["Emissions|CO2" ] = co2_emissions .sum () - co2_atmosphere_withdrawal .sum ()
2552
+
2553
+ assert isclose (
2554
+ var ["Emissions|CO2" ],
2555
+ var ["Emissions|CO2|Model" ]
2556
+ + co2_storage .sum () * ccu_fraction
2557
+ - var ["Emissions|CO2|Efuels|Liquids" ]
2558
+ - var ["Emissions|CO2|Efuels|Gases" ],
2559
+ )
2560
+
2561
+ # Split CHP emissions between electricity and heat sectors
2471
2562
2472
2563
CHP_E_to_H = (
2473
2564
n .links .loc [CHP_emissions .index .get_level_values ("name" )].efficiency
@@ -2476,80 +2567,37 @@ def get_emissions(n, region, _energy_totals):
2476
2567
2477
2568
CHP_E_fraction = CHP_E_to_H * (1 / (CHP_E_to_H + 1 ))
2478
2569
2479
- ccs_carriers = co2_storage .index .intersection (co2_atmosphere_withdrawal .index )
2480
- fossil_cc_carriers = co2_storage .index .difference (co2_atmosphere_withdrawal .index )
2570
+ negative_CHP_E_to_H = (
2571
+ n .links .loc [CHP_atmosphere_withdrawal .index .get_level_values ("name" )].efficiency
2572
+ / n .links .loc [
2573
+ CHP_atmosphere_withdrawal .index .get_level_values ("name" )
2574
+ ].efficiency2
2575
+ )
2481
2576
2482
- negative_cc = co2_storage .reindex (ccs_carriers )
2483
- fossil_cc = co2_storage .reindex (fossil_cc_carriers )
2577
+ negative_CHP_E_fraction = negative_CHP_E_to_H * (1 / (negative_CHP_E_to_H + 1 ))
2484
2578
2485
- assert isclose (fossil_cc .sum () + negative_cc .sum (), co2_storage .sum ())
2579
+ # separate waste CHPs, because they are accounted differently
2580
+ waste_CHP_emissions = CHP_emissions .filter (like = "waste" )
2581
+ CHP_emissions = CHP_emissions .drop (waste_CHP_emissions .index )
2486
2582
2487
- try :
2488
- total_ccs = (
2489
- n .statistics .supply (bus_carrier = "co2 sequestered" , ** kwargs )
2490
- .filter (like = region )
2491
- .get ("Link" )
2492
- .groupby ("carrier" )
2493
- .sum ()
2494
- .multiply (t2Mt )
2495
- .sum ()
2496
- )
2497
- except AttributeError : # no sequestration in 2020 -> NoneType
2498
- total_ccs = 0.0
2583
+ # It would be interesting to relate the Emissions|CO2|Model to Emissions|CO2 reported to the DB by considering imports of carbon, e.g., (exports_oil_renew - imports_oil_renew) * 0.2571 * t2Mt + (exports_gas_renew - imports_gas_renew) * 0.2571 * t2Mt + (exports_meoh - imports_meoh) / 4.0321 * t2Mt
2584
+ # Then it would be necessary to consider negative carbon from solid biomass imports as well
2585
+ # Actually we might have to include solid biomass imports in the co2 constraints as well
2499
2586
2500
- negative_ccs = total_ccs - fossil_cc .sum ()
2501
-
2502
- co2_negative_emissions = negative_cc .multiply (negative_ccs / negative_cc .sum ())
2503
-
2504
- if negative_ccs < 0 :
2505
- co2_negative_emissions *= 0
2506
- # If not enough CO2 is captured, than additional emissions occur
2507
- fossil_cc_emissions = - fossil_cc .multiply (negative_ccs / fossil_cc .sum ())
2508
- # All captured fossil should be sequestered for e-fuels to be carbon neutral
2509
- # If this warning appears repeatedly we will need to add a hard constraint
2510
- print ("WARNING! Not all CO2 capture from fossil sources is captured!!!" )
2511
- print ("total_ccs - fossil_cc: " , total_ccs - fossil_cc .sum ())
2512
- # TODO what to do with fossil_cc_emissions???
2513
- if (n .links .build_year .max () == 2045 ) and (
2514
- total_ccs - fossil_cc .sum () > 1
2515
- ): # > 1 for numerical errors
2516
- raise Exception ("Not enough CCS in 2045!" )
2517
-
2518
- if not co2_atmosphere_withdrawal .get ("urban central solid biomass CHP CC" ):
2519
- biomass_CHP_correction_factor = 0
2520
- else :
2521
- biomass_CHP_correction_factor = min (
2522
- 1 , # Can not be > 1, taking minimum to avoid numerical errors
2523
- co2_negative_emissions .get ("urban central solid biomass CHP CC" )
2524
- / co2_atmosphere_withdrawal .get ("urban central solid biomass CHP CC" ),
2525
- )
2526
-
2527
- negative_CHP_emissions = (
2528
- n .statistics .withdrawal (bus_carrier = "co2" , ** kwargs )
2529
- .filter (like = region )
2530
- .filter (like = "solid biomass CHP CC" )
2531
- .multiply (t2Mt )
2532
- .multiply ( # Correcting for actual negative emissions
2533
- biomass_CHP_correction_factor
2534
- )
2587
+ assert isclose (
2588
+ co2_emissions .filter (like = "CHP" ).sum (),
2589
+ CHP_emissions .sum () + waste_CHP_emissions .sum (),
2535
2590
)
2536
-
2537
- negative_CHP_E_to_H = (
2538
- n .links .loc [negative_CHP_emissions .index .get_level_values ("name" )].efficiency
2539
- / n .links .loc [negative_CHP_emissions .index .get_level_values ("name" )].efficiency2
2591
+ assert isclose (
2592
+ co2_atmosphere_withdrawal .filter (like = "CHP" ).sum (),
2593
+ CHP_atmosphere_withdrawal .sum (),
2540
2594
)
2541
2595
2542
- negative_CHP_E_fraction = negative_CHP_E_to_H * (1 / (negative_CHP_E_to_H + 1 ))
2543
-
2544
2596
var ["Carbon Sequestration" ] = total_ccs
2545
2597
2546
- var ["Carbon Sequestration|DACCS" ] = var ["Carbon Sequestration" ] * (
2547
- co2_storage .filter (like = "DAC" ).sum () / co2_storage .sum ()
2548
- )
2598
+ var ["Carbon Sequestration|DACCS" ] = co2_storage .filter (like = "DAC" ).sum ()
2549
2599
2550
- var ["Carbon Sequestration|BECCS" ] = var ["Carbon Sequestration" ] * (
2551
- co2_storage .filter (like = "bio" ).sum () / co2_storage .sum ()
2552
- )
2600
+ var ["Carbon Sequestration|BECCS" ] = co2_storage .filter (like = "bio" ).sum ()
2553
2601
2554
2602
var ["Carbon Sequestration|Other" ] = (
2555
2603
var ["Carbon Sequestration" ]
@@ -2568,10 +2616,11 @@ def get_emissions(n, region, _energy_totals):
2568
2616
]
2569
2617
).sum () + co2_emissions .get ("industry methanol" , 0 )
2570
2618
# process emissions is mainly cement, methanol is used for chemicals
2619
+ # TODO where should the methanol go?
2571
2620
2572
2621
var ["Emissions|CO2|Energy|Demand|Industry" ] = co2_emissions .reindex (
2573
2622
["gas for industry" , "gas for industry CC" , "coal for industry" ]
2574
- ).sum () - co2_negative_emissions .get (
2623
+ ).sum () - co2_atmosphere_withdrawal .get (
2575
2624
"solid biomass for industry CC" ,
2576
2625
0 ,
2577
2626
)
@@ -2658,19 +2707,17 @@ def get_emissions(n, region, _energy_totals):
2658
2707
2659
2708
var ["Emissions|CO2|Energy|Supply|Electricity" ] = (
2660
2709
var ["Emissions|Gross Fossil CO2|Energy|Supply|Electricity" ]
2661
- - negative_CHP_emissions .multiply (negative_CHP_E_fraction ).values .sum ()
2710
+ - CHP_atmosphere_withdrawal .multiply (negative_CHP_E_fraction ).values .sum ()
2662
2711
)
2663
2712
2664
2713
var ["Emissions|Gross Fossil CO2|Energy|Supply|Heat" ] = (
2665
- co2_emissions .filter (like = "urban central" )
2666
- .filter (like = "boiler" ) # in 2020 there might be central oil boilers?!
2667
- .sum ()
2714
+ co2_emissions .filter (like = "urban central" ).filter (like = "boiler" ).sum ()
2668
2715
+ CHP_emissions .multiply (1 - CHP_E_fraction ).values .sum ()
2669
2716
)
2670
2717
2671
2718
var ["Emissions|CO2|Energy|Supply|Heat" ] = (
2672
2719
var ["Emissions|Gross Fossil CO2|Energy|Supply|Heat" ]
2673
- - negative_CHP_emissions .multiply (1 - negative_CHP_E_fraction ).values .sum ()
2720
+ - CHP_atmosphere_withdrawal .multiply (1 - negative_CHP_E_fraction ).values .sum ()
2674
2721
)
2675
2722
2676
2723
var ["Emissions|CO2|Energy|Supply|Electricity and Heat" ] = (
@@ -2682,24 +2729,18 @@ def get_emissions(n, region, _energy_totals):
2682
2729
"Emissions|Gross Fossil CO2|Energy|Supply|Hydrogen"
2683
2730
] = co2_emissions .filter (like = "SMR" ).sum ()
2684
2731
2685
- var ["Emissions|CO2|Energy|Supply|Gases" ] = (- 1 ) * co2_negative_emissions .filter (
2732
+ var ["Emissions|CO2|Energy|Supply|Gases" ] = (- 1 ) * co2_atmosphere_withdrawal .filter (
2686
2733
like = "biogas to gas"
2687
2734
).sum ()
2688
2735
2689
- var ["Emissions|CO2|Supply|Non-Renewable Waste" ] = co2_emissions .reindex (
2690
- [
2691
- "HVC to air" ,
2692
- "waste CHP" ,
2693
- "waste CHP CC" ,
2694
- ]
2695
- ).sum ()
2736
+ var ["Emissions|CO2|Supply|Non-Renewable Waste" ] = (
2737
+ co2_emissions .get ("HVC to air" ).sum () + waste_CHP_emissions .sum ()
2738
+ )
2696
2739
2697
2740
var ["Emissions|CO2|Energy|Supply|Liquids and Gases" ] = var [
2698
2741
"Emissions|CO2|Energy|Supply|Liquids"
2699
2742
] = co2_emissions .get ("oil refining" , 0 )
2700
2743
2701
- # var["Emissions|CO2|Energy|Supply|Gases"] + \
2702
-
2703
2744
var ["Emissions|CO2|Energy|Supply" ] = (
2704
2745
var ["Emissions|CO2|Energy|Supply|Gases" ]
2705
2746
+ var ["Emissions|CO2|Energy|Supply|Hydrogen" ]
@@ -2733,21 +2774,20 @@ def get_emissions(n, region, _energy_totals):
2733
2774
var ["Emissions|CO2|Energy and Industrial Processes" ]
2734
2775
+ var ["Emissions|CO2|Energy|Demand|Bunkers" ]
2735
2776
+ var ["Emissions|CO2|Supply|Non-Renewable Waste" ]
2736
- - co2_negative_emissions .get ("DAC" , 0 )
2737
- + var ["Emissions|CO2|Energy|Production|From Liquids" ]
2738
- + var ["Emissions|CO2|Energy|Production|From Gases" ]
2739
- - co2_atmosphere_withdrawal .subtract (co2_negative_emissions ).sum ()
2777
+ - co2_atmosphere_withdrawal .get ("DAC" , 0 )
2740
2778
)
2779
+
2741
2780
print (
2742
2781
"Differences in accounting for CO2 emissions:" ,
2743
2782
emission_difference ,
2744
2783
)
2745
2784
2746
- assert emission_difference < 1e-2 # Improve numerical stability
2785
+ assert abs ( emission_difference ) < 1e-5
2747
2786
2748
2787
return var
2749
2788
2750
2789
2790
+
2751
2791
# functions for prices
2752
2792
def get_nodal_flows (n , bus_carrier , region , query = "index == index or index != index" ):
2753
2793
"""
@@ -3879,6 +3919,16 @@ def get_export_import_links(n, region, carriers):
3879
3919
3880
3920
# TODO add methanol trade, renewable gas trade
3881
3921
3922
+ exports_meoh , imports_meoh = get_export_import_links (n , region , ["methanol" ])
3923
+
3924
+ var ["Trade|Secondary Energy|Methanol|Hydrogen|Volume" ] = (
3925
+ exports_meoh - imports_meoh
3926
+ ) * MWh2PJ
3927
+
3928
+ var ["Trade|Secondary Energy|Methanol|Hydrogen|Gross Import|Volume" ] = (
3929
+ imports_meoh * MWh2PJ
3930
+ )
3931
+
3882
3932
# Trade|Primary Energy|Coal|Volume
3883
3933
# Trade|Primary Energy|Gas|Volume
3884
3934
0 commit comments