@@ -644,134 +644,207 @@ def file_upload(label: str, extensions: list[str], key: str,
644644 py_app = app_key
645645 py_netfile = netfile or "<upload network file>"
646646
647- lines = [
647+ # Map UI initialization names to Python API enum names
648+ _init_to_enum = {
649+ "MIDPOINT" : "OPFLOWINIT_MIDPOINT" ,
650+ "FROMFILE" : "OPFLOWINIT_FROMFILE" ,
651+ "ACPF" : "OPFLOWINIT_ACPF" ,
652+ "FLATSTART" : "OPFLOWINIT_FLATSTART" ,
653+ "DCOPF" : "OPFLOWINIT_DCOPF" ,
654+ }
655+ # Map UI output format names to Python API constants
656+ _fmt_to_enum = {
657+ "MATPOWER" : "exago.MATPOWER" ,
658+ "JSON" : "exago.JSON" ,
659+ "CSV" : "exago.CSV" ,
660+ }
661+
662+ # ── Build environment comment block from config ──────────────────────
663+ env_cfg = cfg .get ("environment" , {}) or {}
664+ env_comment_lines = [
665+ "#!/usr/bin/env python3" ,
666+ "# ExaGO Python API Script" ,
667+ "# Generated by ExaGO Launcher" ,
668+ "#" ,
669+ "# Required environment variables (set these before running):" ,
670+ ]
671+ py_paths = env_cfg .get ("pythonpath" , []) or []
672+ if py_paths :
673+ env_comment_lines .append (f"# export PYTHONPATH={ ':' .join (py_paths )} :$PYTHONPATH" )
674+ ld_paths = env_cfg .get ("ld_library_path" , []) or []
675+ if ld_paths :
676+ env_comment_lines .append (f"# export LD_LIBRARY_PATH={ ':' .join (ld_paths )} :$LD_LIBRARY_PATH" )
677+ bin_paths = env_cfg .get ("path" , []) or []
678+ if bin_paths :
679+ env_comment_lines .append (f"# export PATH={ ':' .join (bin_paths )} :$PATH" )
680+
681+ # ── Common preamble ──────────────────────────────────────────────────
682+ lines = env_comment_lines + [
683+ "" ,
648684 "import exago" ,
649685 "" ,
686+ f'# Initialize ExaGO (sets up PETSc/MPI)' ,
687+ f'exago.initialize("{ py_app .lower ()} ")' ,
688+ "" ,
650689 ]
651690
691+ # ── Body lines (filled per application, finalize appended after) ─────
692+ body = []
693+
652694 if py_app == "OPFLOW" :
653- lines += [
695+ body += [
654696 "# Create OPFLOW instance" ,
655697 "opf = exago.OPFLOW()" ,
656698 "" ,
657699 f'opf.read_mat_power_data("{ py_netfile } ")' ,
658700 "" ,
659701 ]
660- # Add model/solver/init from session state
661702 model_val = st .session_state .get ("opf_model" , "POWER_BALANCE_POLAR" )
662703 solver_val = st .session_state .get ("opf_solver" , "IPOPT" )
663704 init_val = st .session_state .get ("opf_init" , "MIDPOINT" )
705+ init_enum = _init_to_enum .get (init_val , f"OPFLOWINIT_{ init_val } " )
664706
665- lines .append (f'opf.set_model("{ model_val } ")' )
666- lines .append (f'opf.set_solver("{ solver_val } ")' )
667- lines .append (f'opf.set_initialization ("{ init_val } ")' )
668- lines .append ("" )
707+ body .append (f'opf.set_model("{ model_val } ")' )
708+ body .append (f'opf.set_solver("{ solver_val } ")' )
709+ body .append (f'opf.set_initialization_type ("{ init_enum } ")' )
710+ body .append ("" )
669711
670712 if st .session_state .get ("opf_ignore_lf" ):
671- lines .append ("opf.set_ignore_lineflow_constraints(True)" )
713+ body .append ("opf.set_ignore_lineflow_constraints(True)" )
672714 if st .session_state .get ("opf_loadloss" ):
673- lines .append ("opf.set_include_loadloss_variables (True)" )
674- lines .append (f"opf.set_loadloss_penalty({ st .session_state .get ('opf_ll_pen' , 1000.0 )} )" )
715+ body .append ("opf.set_has_loadloss (True)" )
716+ body .append (f"opf.set_loadloss_penalty({ st .session_state .get ('opf_ll_pen' , 1000.0 )} )" )
675717 if st .session_state .get ("opf_pwrimb" ):
676- lines .append ("opf.set_include_powerimbalance_variables (True)" )
677- lines .append (f"opf.set_powerimbalance_penalty ({ st .session_state .get ('opf_pi_pen' , 10000.0 )} )" )
718+ body .append ("opf.set_has_bus_power_imbalance (True)" )
719+ body .append (f"opf.set_bus_power_imbalance_penalty ({ st .session_state .get ('opf_pi_pen' , 10000.0 )} )" )
678720
679721 genbus_val = st .session_state .get ("opf_genbus" , "VARIABLE_WITHIN_BOUNDS" )
680- lines .append (f'opf.set_genbusvoltage ("{ genbus_val } ")' )
722+ body .append (f'opf.set_gen_bus_voltage_type ("{ genbus_val } ")' )
681723
682724 obj_val = st .session_state .get ("opf_obj" , "MIN_GEN_COST" )
683- lines .append (f'opf.set_objective ("{ obj_val } ")' )
725+ body .append (f'opf.set_objective_type ("{ obj_val } ")' )
684726
685727 tol_val = st .session_state .get ("opf_tol" , 1e-6 )
686- lines .append (f"opf.set_tolerance({ tol_val } )" )
687- lines .append ("" )
688- lines .append ("# Solve" )
689- lines .append ("opf.solve()" )
690- lines .append ("" )
691- lines .append ("# Print solution" )
728+ body .append (f"opf.set_tolerance({ tol_val } )" )
729+ body .append ("" )
730+ body .append ("# Solve" )
731+ body .append ("opf.solve()" )
732+ body .append ("" )
692733 if print_output :
693- lines .append ("opf.print_solution()" )
734+ body .append ("# Print solution" )
735+ body .append ("opf.print_solution()" )
694736 if save_output :
695737 fmt = output_format or "MATPOWER"
696- lines .append (f'opf.save_solution("{ fmt } ")' )
738+ fmt_enum = _fmt_to_enum .get (fmt , f"exago.{ fmt } " )
739+ body .append (f"opf.save_solution({ fmt_enum } )" )
697740
698741 elif py_app == "SCOPFLOW" :
699- lines += [
742+ body += [
700743 "# Create SCOPFLOW instance" ,
701744 "scopf = exago.SCOPFLOW()" ,
702745 "" ,
703- f'scopf.read_mat_power_data ("{ py_netfile } ")' ,
746+ f'scopf.set_network_data ("{ py_netfile } ")' ,
704747 "" ,
705748 ]
706749 if st .session_state .get ("scop_ctg" ):
707- lines .append (f'scopf.set_contingency_data("{ st .session_state ["scop_ctg" ]} ")' )
750+ body .append (f'scopf.set_contingency_data("{ st .session_state ["scop_ctg" ]} ", exago.ContingencyFileInputFormat.NATIVE )' )
708751 nc = st .session_state .get ("scop_nc" , - 1 )
709- lines .append (f"scopf.set_num_contingencies({ nc } )" )
752+ body .append (f"scopf.set_num_contingencies({ nc } )" )
710753 solver_val = st .session_state .get ("scop_solver" , "IPOPT" )
711- lines .append (f'scopf.set_solver("{ solver_val } ")' )
754+ body .append (f'scopf.set_solver("{ solver_val } ")' )
712755 mode_val = st .session_state .get ("scop_mode" , "0 - Preventive" )
713- lines .append (f"scopf.set_mode({ mode_val [0 ]} )" )
714- lines .append ("" )
715- lines .append ("scopf.solve()" )
756+ body .append (f"scopf.set_mode({ mode_val [0 ]} )" )
757+ body .append ("" )
758+ body .append ("# Solve" )
759+ body .append ("scopf.solve()" )
716760 if print_output :
717- lines .append ("scopf.print_solution()" )
761+ body .append ("" )
762+ body .append ("# Print solution" )
763+ body .append ("scopf.print_solution(0)" )
718764
719765 elif py_app == "SOPFLOW" :
720- lines += [
766+ body += [
721767 "# Create SOPFLOW instance" ,
722768 "sopf = exago.SOPFLOW()" ,
723769 "" ,
724- f'sopf.read_mat_power_data ("{ py_netfile } ")' ,
770+ f'sopf.set_network_data ("{ py_netfile } ")' ,
725771 "" ,
726772 ]
727773 if st .session_state .get ("sop_scen" ):
728- lines .append (f'sopf.set_wind_gen_data ("{ st .session_state ["sop_scen" ]} ")' )
774+ body .append (f'sopf.set_scenario_data ("{ st .session_state ["sop_scen" ]} ", exago.ScenarioFileInputFormat.NATIVE_SINGLEPERIOD, exago.ScenarioUncertaintyType.WIND )' )
729775 ns = st .session_state .get ("sop_ns" , - 1 )
730- lines .append (f"sopf.set_num_scenarios({ ns } )" )
776+ body .append (f"sopf.set_num_scenarios({ ns } )" )
731777 if st .session_state .get ("sop_ctg" ):
732- lines .append (f'sopf.set_contingency_data("{ st .session_state ["sop_ctg" ]} ")' )
778+ body .append (f'sopf.set_contingency_data("{ st .session_state ["sop_ctg" ]} ", exago.ContingencyFileInputFormat.NATIVE )' )
733779 nc = st .session_state .get ("sop_nc" , - 1 )
734- lines .append (f"sopf.set_num_contingencies({ nc } )" )
780+ body .append (f"sopf.set_num_contingencies({ nc } )" )
735781 solver_val = st .session_state .get ("sop_solver" , "IPOPT" )
736- lines .append (f'sopf.set_solver("{ solver_val } ")' )
737- lines .append ("" )
738- lines .append ("sopf.solve()" )
782+ body .append (f'sopf.set_solver("{ solver_val } ")' )
783+ body .append ("" )
784+ body .append ("# Solve" )
785+ body .append ("sopf.solve()" )
739786 if print_output :
740- lines .append ("sopf.print_solution()" )
787+ body .append ("" )
788+ body .append ("# Print solution" )
789+ body .append ("sopf.print_solution(0)" )
741790
742791 elif py_app == "TCOPFLOW" :
743- lines += [
792+ body += [
744793 "# Create TCOPFLOW instance" ,
745794 "tcopf = exago.TCOPFLOW()" ,
746795 "" ,
747- f'tcopf.read_mat_power_data ("{ py_netfile } ")' ,
796+ f'tcopf.set_network_data ("{ py_netfile } ")' ,
748797 "" ,
749798 ]
750- if st .session_state .get ("tc_lp" ):
751- lines . append ( f'tcopf.set_pload_profile(" { st . session_state [ "tc_lp" ] } ")' )
752- if st . session_state . get ( " tc_lq" ) :
753- lines .append (f'tcopf.set_qload_profile ("{ st . session_state [ "tc_lq" ] } ")' )
799+ tc_lp = st .session_state .get ("tc_lp" )
800+ tc_lq = st . session_state . get ( "tc_lq" )
801+ if tc_lp and tc_lq :
802+ body .append (f'tcopf.set_load_profiles ("{ tc_lp } ", " { tc_lq } ")' )
754803 dt_val = st .session_state .get ("tc_dt" , 5.0 )
755804 dur_val = st .session_state .get ("tc_dur" , 0.5 )
756- lines .append (f"tcopf.set_time_step ({ dt_val } )" )
757- lines .append (f"tcopf.set_duration( { dur_val } ) " )
758- lines .append ("" )
759- lines .append ("tcopf.solve()" )
805+ body .append (f"tcopf.set_time_step_and_duration ({ dt_val } , { dur_val } )" )
806+ body .append (" " )
807+ body .append ("# Solve " )
808+ body .append ("tcopf.solve()" )
760809 if print_output :
761- lines .append ("tcopf.print_solution()" )
762-
763- elif py_app in ("PFLOW" , "DCOPFLOW" ):
764- app_lower = py_app .lower ()
765- lines += [
766- f"# Create { py_app } instance" ,
767- f"pf = exago.{ py_app } ()" ,
810+ body .append ("" )
811+ body .append ("# Print solution" )
812+ body .append ("tcopf.print_solution(0)" )
813+
814+ elif py_app == "PFLOW" :
815+ body += [
816+ "# Create PFLOW instance" ,
817+ "pf = exago.PFLOW()" ,
768818 "" ,
769819 f'pf.read_mat_power_data("{ py_netfile } ")' ,
770820 "" ,
821+ "# Solve" ,
771822 "pf.solve()" ,
772823 ]
773824 if print_output :
774- lines .append ("pf.print_solution()" )
825+ body .append ("" )
826+ body .append ("# Print solution" )
827+ body .append ("pf.print_solution()" )
828+
829+ elif py_app == "DCOPFLOW" :
830+ # DCOPFLOW has no Python bindings — skip init/finalize wrapper
831+ lines = [
832+ "# NOTE: DCOPFLOW does not have Python API bindings." ,
833+ "# Use the command-line interface instead (see Command Preview in the Run Simulation tab)." ,
834+ ]
835+ body = []
836+
837+ # Assemble: preamble + body + finalize
838+ if body :
839+ lines += body
840+ lines += [
841+ "" ,
842+ "# Clean up" ,
843+ "try:" ,
844+ " exago.finalize()" ,
845+ "except Exception:" ,
846+ " pass # MPI finalization may throw during cleanup; results are valid" ,
847+ ]
775848
776849 python_code = "\n " .join (lines )
777850 st .code (python_code , language = "python" )
0 commit comments