Conditions | 15 |
Total Lines | 348 |
Code Lines | 225 |
Lines | 0 |
Ratio | 0 % |
Changes | 0 |
Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.
For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.
Commonly applied refactorings include:
If many parameters/temporary variables are present:
Complex classes like data.datasets.emobility.motorized_individual_travel.model_timeseries.write_model_data_to_db() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
1 | """ |
||
501 | def write_model_data_to_db( |
||
502 | static_params_dict: dict, |
||
503 | load_time_series_df: pd.DataFrame, |
||
504 | bus_id: int, |
||
505 | scenario_name: str, |
||
506 | run_config: pd.DataFrame, |
||
507 | bat_cap: pd.DataFrame, |
||
508 | ) -> None: |
||
509 | """Write all results for grid district to database |
||
510 | |||
511 | Parameters |
||
512 | ---------- |
||
513 | static_params_dict : dict |
||
514 | Static model params |
||
515 | load_time_series_df : pd.DataFrame |
||
516 | Load time series for grid district |
||
517 | bus_id : int |
||
518 | ID of grid district |
||
519 | scenario_name : str |
||
520 | Scenario name |
||
521 | run_config : pd.DataFrame |
||
522 | simBEV metadata: run config |
||
523 | bat_cap : pd.DataFrame |
||
524 | Battery capacities per EV type |
||
525 | |||
526 | Returns |
||
527 | ------- |
||
528 | None |
||
529 | """ |
||
530 | |||
531 | def calc_initial_ev_soc(bus_id: int, scenario_name: str) -> pd.DataFrame: |
||
532 | """Calculate an average initial state of charge for EVs in MV grid |
||
533 | district. |
||
534 | |||
535 | This is done by weighting the initial SoCs at timestep=0 with EV count |
||
536 | and battery capacity for each EV type. |
||
537 | """ |
||
538 | with db.session_scope() as session: |
||
539 | query_ev_soc = ( |
||
540 | session.query( |
||
541 | EgonEvPool.type, |
||
542 | func.count(EgonEvTrip.egon_ev_pool_ev_id).label( |
||
543 | "ev_count" |
||
544 | ), |
||
545 | func.avg(EgonEvTrip.soc_start).label("ev_soc_start"), |
||
546 | ) |
||
547 | .select_from(EgonEvTrip) |
||
548 | .join( |
||
549 | EgonEvPool, |
||
550 | EgonEvPool.ev_id == EgonEvTrip.egon_ev_pool_ev_id, |
||
551 | ) |
||
552 | .join( |
||
553 | EgonEvMvGridDistrict, |
||
554 | EgonEvMvGridDistrict.egon_ev_pool_ev_id |
||
555 | == EgonEvTrip.egon_ev_pool_ev_id, |
||
556 | ) |
||
557 | .filter( |
||
558 | EgonEvTrip.scenario == scenario_name, |
||
559 | EgonEvPool.scenario == scenario_name, |
||
560 | EgonEvMvGridDistrict.scenario == scenario_name, |
||
561 | EgonEvMvGridDistrict.bus_id == bus_id, |
||
562 | EgonEvTrip.simbev_event_id == 0, |
||
563 | ) |
||
564 | .group_by(EgonEvPool.type) |
||
565 | ) |
||
566 | |||
567 | initial_soc_per_ev_type = pd.read_sql( |
||
568 | query_ev_soc.statement, query_ev_soc.session.bind, index_col="type" |
||
569 | ) |
||
570 | |||
571 | initial_soc_per_ev_type[ |
||
572 | "battery_capacity_sum" |
||
573 | ] = initial_soc_per_ev_type.ev_count.multiply(bat_cap) |
||
574 | initial_soc_per_ev_type[ |
||
575 | "ev_soc_start_abs" |
||
576 | ] = initial_soc_per_ev_type.battery_capacity_sum.multiply( |
||
577 | initial_soc_per_ev_type.ev_soc_start |
||
578 | ) |
||
579 | |||
580 | return ( |
||
581 | initial_soc_per_ev_type.ev_soc_start_abs.sum() |
||
582 | / initial_soc_per_ev_type.battery_capacity_sum.sum() |
||
583 | ) |
||
584 | |||
585 | def write_to_db(write_lowflex_model: bool) -> None: |
||
586 | """Write model data to eTraGo tables""" |
||
587 | |||
588 | @db.check_db_unique_violation |
||
589 | def write_bus(scenario_name: str) -> int: |
||
590 | # eMob MIT bus |
||
591 | emob_bus_id = db.next_etrago_id("bus") |
||
592 | with db.session_scope() as session: |
||
593 | session.add( |
||
594 | EgonPfHvBus( |
||
595 | scn_name=scenario_name, |
||
596 | bus_id=emob_bus_id, |
||
597 | v_nom=1, |
||
598 | carrier="Li_ion", |
||
599 | x=etrago_bus.x, |
||
600 | y=etrago_bus.y, |
||
601 | geom=etrago_bus.geom, |
||
602 | ) |
||
603 | ) |
||
604 | return emob_bus_id |
||
605 | |||
606 | @db.check_db_unique_violation |
||
607 | def write_link(scenario_name: str) -> None: |
||
608 | # eMob MIT link [bus_el] -> [bus_ev] |
||
609 | emob_link_id = db.next_etrago_id("link") |
||
610 | with db.session_scope() as session: |
||
611 | session.add( |
||
612 | EgonPfHvLink( |
||
613 | scn_name=scenario_name, |
||
614 | link_id=emob_link_id, |
||
615 | bus0=etrago_bus.bus_id, |
||
616 | bus1=emob_bus_id, |
||
617 | carrier="BEV_charger", |
||
618 | efficiency=float(run_config.eta_cp), |
||
619 | p_nom=( |
||
620 | load_time_series_df.simultaneous_plugged_in_charging_capacity.max() # noqa: E501 |
||
621 | ), |
||
622 | p_nom_extendable=False, |
||
623 | p_nom_min=0, |
||
624 | p_nom_max=np.Inf, |
||
625 | p_min_pu=0, |
||
626 | p_max_pu=1, |
||
627 | # p_set_fixed=0, |
||
628 | capital_cost=0, |
||
629 | marginal_cost=0, |
||
630 | length=0, |
||
631 | terrain_factor=1, |
||
632 | ) |
||
633 | ) |
||
634 | with db.session_scope() as session: |
||
635 | session.add( |
||
636 | EgonPfHvLinkTimeseries( |
||
637 | scn_name=scenario_name, |
||
638 | link_id=emob_link_id, |
||
639 | temp_id=1, |
||
640 | p_min_pu=None, |
||
641 | p_max_pu=( |
||
642 | hourly_load_time_series_df.ev_availability.to_list() # noqa: E501 |
||
643 | ), |
||
644 | ) |
||
645 | ) |
||
646 | |||
647 | @db.check_db_unique_violation |
||
648 | def write_store(scenario_name: str) -> None: |
||
649 | # eMob MIT store |
||
650 | emob_store_id = db.next_etrago_id("store") |
||
651 | with db.session_scope() as session: |
||
652 | session.add( |
||
653 | EgonPfHvStore( |
||
654 | scn_name=scenario_name, |
||
655 | store_id=emob_store_id, |
||
656 | bus=emob_bus_id, |
||
657 | carrier="battery_storage", |
||
658 | e_nom=static_params_dict["store_ev_battery.e_nom_MWh"], |
||
659 | e_nom_extendable=False, |
||
660 | e_nom_min=0, |
||
661 | e_nom_max=np.Inf, |
||
662 | e_min_pu=0, |
||
663 | e_max_pu=1, |
||
664 | e_initial=( |
||
665 | initial_soc_mean |
||
666 | * static_params_dict["store_ev_battery.e_nom_MWh"] |
||
667 | ), |
||
668 | e_cyclic=False, |
||
669 | sign=1, |
||
670 | standing_loss=0, |
||
671 | ) |
||
672 | ) |
||
673 | with db.session_scope() as session: |
||
674 | session.add( |
||
675 | EgonPfHvStoreTimeseries( |
||
676 | scn_name=scenario_name, |
||
677 | store_id=emob_store_id, |
||
678 | temp_id=1, |
||
679 | e_min_pu=hourly_load_time_series_df.soc_min.to_list(), |
||
680 | e_max_pu=hourly_load_time_series_df.soc_max.to_list(), |
||
681 | ) |
||
682 | ) |
||
683 | |||
684 | @db.check_db_unique_violation |
||
685 | def write_load( |
||
686 | scenario_name: str, connection_bus_id: int, load_ts: list |
||
687 | ) -> None: |
||
688 | # eMob MIT load |
||
689 | emob_load_id = db.next_etrago_id("load") |
||
690 | with db.session_scope() as session: |
||
691 | session.add( |
||
692 | EgonPfHvLoad( |
||
693 | scn_name=scenario_name, |
||
694 | load_id=emob_load_id, |
||
695 | bus=connection_bus_id, |
||
696 | carrier="land_transport_EV", |
||
697 | sign=-1, |
||
698 | ) |
||
699 | ) |
||
700 | with db.session_scope() as session: |
||
701 | session.add( |
||
702 | EgonPfHvLoadTimeseries( |
||
703 | scn_name=scenario_name, |
||
704 | load_id=emob_load_id, |
||
705 | temp_id=1, |
||
706 | p_set=load_ts, |
||
707 | ) |
||
708 | ) |
||
709 | |||
710 | # Get eTraGo substation bus |
||
711 | with db.session_scope() as session: |
||
712 | query = session.query( |
||
713 | EgonPfHvBus.scn_name, |
||
714 | EgonPfHvBus.bus_id, |
||
715 | EgonPfHvBus.x, |
||
716 | EgonPfHvBus.y, |
||
717 | EgonPfHvBus.geom, |
||
718 | ).filter( |
||
719 | EgonPfHvBus.scn_name == scenario_name, |
||
720 | EgonPfHvBus.bus_id == bus_id, |
||
721 | EgonPfHvBus.carrier == "AC", |
||
722 | ) |
||
723 | etrago_bus = query.first() |
||
724 | if etrago_bus is None: |
||
725 | # TODO: raise exception here! |
||
726 | print( |
||
727 | f"No AC bus found for scenario {scenario_name} " |
||
728 | f"with bus_id {bus_id} in table egon_etrago_bus!" |
||
729 | ) |
||
730 | |||
731 | # Call DB writing functions for regular or lowflex scenario |
||
732 | # * use corresponding scenario name as defined in datasets.yml |
||
733 | # * no storage for lowflex scenario |
||
734 | # * load timeseries: |
||
735 | # * regular (flex): use driving load |
||
736 | # * lowflex: use dumb charging load |
||
737 | if write_lowflex_model is False: |
||
738 | emob_bus_id = write_bus(scenario_name=scenario_name) |
||
739 | write_link(scenario_name=scenario_name) |
||
740 | write_store(scenario_name=scenario_name) |
||
741 | write_load( |
||
742 | scenario_name=scenario_name, |
||
743 | connection_bus_id=emob_bus_id, |
||
744 | load_ts=( |
||
745 | hourly_load_time_series_df.driving_load_time_series.to_list() # noqa: E501 |
||
746 | ), |
||
747 | ) |
||
748 | else: |
||
749 | # Get lowflex scenario name |
||
750 | lowflex_scenario_name = DATASET_CFG["scenario"]["lowflex"][ |
||
751 | "names" |
||
752 | ][scenario_name] |
||
753 | write_load( |
||
754 | scenario_name=lowflex_scenario_name, |
||
755 | connection_bus_id=etrago_bus.bus_id, |
||
756 | load_ts=hourly_load_time_series_df.load_time_series.to_list(), |
||
757 | ) |
||
758 | |||
759 | def write_to_file(): |
||
760 | """Write model data to file (for debugging purposes)""" |
||
761 | results_dir = WORKING_DIR / Path("results", scenario_name, str(bus_id)) |
||
762 | results_dir.mkdir(exist_ok=True, parents=True) |
||
763 | |||
764 | hourly_load_time_series_df[["load_time_series"]].to_csv( |
||
765 | results_dir / "ev_load_time_series.csv" |
||
766 | ) |
||
767 | hourly_load_time_series_df[["ev_availability"]].to_csv( |
||
768 | results_dir / "ev_availability.csv" |
||
769 | ) |
||
770 | hourly_load_time_series_df[["soc_min", "soc_max"]].to_csv( |
||
771 | results_dir / "ev_dsm_profile.csv" |
||
772 | ) |
||
773 | |||
774 | static_params_dict[ |
||
775 | "load_land_transport_ev.p_set_MW" |
||
776 | ] = "ev_load_time_series.csv" |
||
777 | static_params_dict["link_bev_charger.p_max_pu"] = "ev_availability.csv" |
||
778 | static_params_dict["store_ev_battery.e_min_pu"] = "ev_dsm_profile.csv" |
||
779 | static_params_dict["store_ev_battery.e_max_pu"] = "ev_dsm_profile.csv" |
||
780 | |||
781 | file = results_dir / "ev_static_params.json" |
||
782 | |||
783 | with open(file, "w") as f: |
||
784 | json.dump(static_params_dict, f, indent=4) |
||
|
|||
785 | |||
786 | print(" Writing model timeseries...") |
||
787 | load_time_series_df = load_time_series_df.assign( |
||
788 | ev_availability=( |
||
789 | load_time_series_df.simultaneous_plugged_in_charging_capacity |
||
790 | / static_params_dict["link_bev_charger.p_nom_MW"] |
||
791 | ) |
||
792 | ) |
||
793 | |||
794 | # Resample to 1h |
||
795 | hourly_load_time_series_df = load_time_series_df.resample("1H").agg( |
||
796 | { |
||
797 | "load_time_series": np.mean, |
||
798 | "flex_time_series": np.mean, |
||
799 | "simultaneous_plugged_in_charging_capacity": np.mean, |
||
800 | "simultaneous_plugged_in_charging_capacity_flex": np.mean, |
||
801 | "soc_min_absolute": np.min, |
||
802 | "soc_max_absolute": np.max, |
||
803 | "ev_availability": np.mean, |
||
804 | "driving_load_time_series": np.sum, |
||
805 | } |
||
806 | ) |
||
807 | |||
808 | # Create relative SoC timeseries |
||
809 | hourly_load_time_series_df = hourly_load_time_series_df.assign( |
||
810 | soc_min=hourly_load_time_series_df.soc_min_absolute.div( |
||
811 | static_params_dict["store_ev_battery.e_nom_MWh"] |
||
812 | ), |
||
813 | soc_max=hourly_load_time_series_df.soc_max_absolute.div( |
||
814 | static_params_dict["store_ev_battery.e_nom_MWh"] |
||
815 | ), |
||
816 | ) |
||
817 | hourly_load_time_series_df = hourly_load_time_series_df.assign( |
||
818 | soc_delta_absolute=( |
||
819 | hourly_load_time_series_df.soc_max_absolute |
||
820 | - hourly_load_time_series_df.soc_min_absolute |
||
821 | ), |
||
822 | soc_delta=( |
||
823 | hourly_load_time_series_df.soc_max |
||
824 | - hourly_load_time_series_df.soc_min |
||
825 | ), |
||
826 | ) |
||
827 | |||
828 | # Crop hourly TS if needed |
||
829 | hourly_load_time_series_df = hourly_load_time_series_df[:8760] |
||
830 | |||
831 | # Create lowflex scenario? |
||
832 | write_lowflex_model = DATASET_CFG["scenario"]["lowflex"][ |
||
833 | "create_lowflex_scenario" |
||
834 | ] |
||
835 | |||
836 | # Get initial average storage SoC |
||
837 | initial_soc_mean = calc_initial_ev_soc(bus_id, scenario_name) |
||
838 | |||
839 | # Write to database: regular and lowflex scenario |
||
840 | write_to_db(write_lowflex_model=False) |
||
841 | print(" Writing flex scenario...") |
||
842 | if write_lowflex_model is True: |
||
843 | print(" Writing lowflex scenario...") |
||
844 | write_to_db(write_lowflex_model=True) |
||
845 | |||
846 | # Export to working dir if requested |
||
847 | if DATASET_CFG["model_timeseries"]["export_results_to_csv"]: |
||
848 | write_to_file() |
||
849 | |||
1080 |