text:
using PSCOPF

using Test
using JuMP
using Dates
using DataStructures

@testset verbose=true "test_tso_start_cost" begin

    #=
    TS: [11h, 11h15]
    S: [S1,S2]
                        bus 1
                        |
    (pilotable) prod_1_1|          load_1
    Pmin=10, Pmax=100   |S1: 30     S1: 50
    Csta=45k, Cprop=10  |S2: 25     S2: 35
                        |
    (pilotable) prod_1_2|
     Pmin=10, Pmax=100  |
     Csta=80k, Cprop=15 |
    =#

    TS = [DateTime("2015-01-01T11:00:00"), DateTime("2015-01-01T11:15:00")]
    ech = DateTime("2015-01-01T07:00:00")
    network = PSCOPF.Networks.Network()
    # Buses
    PSCOPF.Networks.add_new_bus!(network, "bus_1")
    # Pilotables
    PSCOPF.Networks.add_new_generator_to_bus!(network, "bus_1", "prod_1_1", PSCOPF.Networks.PILOTABLE,
                                            10., 100.,
                                            45000., 10.,
                                            Dates.Second(0), Dates.Second(0))
    PSCOPF.Networks.add_new_generator_to_bus!(network, "bus_1", "prod_1_2", PSCOPF.Networks.PILOTABLE,
                                            10., 100.,
                                            80000., 15.,
                                            Dates.Second(0), Dates.Second(0))
    # Uncertainties
    uncertainties = PSCOPF.Uncertainties()
    PSCOPF.add_uncertainty!(uncertainties, ech, "bus_1", DateTime("2015-01-01T11:00:00"), "S1", 30.)
    PSCOPF.add_uncertainty!(uncertainties, ech, "bus_1", DateTime("2015-01-01T11:00:00"), "S2", 25.)
    PSCOPF.add_uncertainty!(uncertainties, ech, "bus_1", DateTime("2015-01-01T11:15:00"), "S1", 50.)
    PSCOPF.add_uncertainty!(uncertainties, ech, "bus_1", DateTime("2015-01-01T11:15:00"), "S2", 35.)
    # firmness
    firmness = PSCOPF.Firmness(
                SortedDict("prod_1_1" => SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.FREE,
                                                    Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.FREE),
                            "prod_1_2" => SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.FREE,
                                                    Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.FREE) ),
                SortedDict( "prod_1_1" => SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.FREE,
                                                    Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.FREE),
                            "prod_1_2" => SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.FREE,
                                                    Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.FREE), )
                )

    #=
    prod_1_1 is ON, prod_1_2 is ON
    Market has not set these units' levels
    We only use prod_1_1 cause its prop cost is lower
    =#
    @testset "tso_no_starting_cost_if_units_initially_started" begin
        # initial generators state
        generators_init_state = SortedDict(
            "prod_1_1" => PSCOPF.ON,
            "prod_1_2" => PSCOPF.ON
        )
        context = PSCOPF.PSCOPFContext(network, TS, PSCOPF.PSCOPF_MODE_1,
                                        generators_init_state,
                                        uncertainties, nothing)
        context.market_schedule = PSCOPF.Schedule(PSCOPF.Market(), ech, SortedDict(
            "prod_1_1" => PSCOPF.GeneratorSchedule("prod_1_1",
                SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.UncertainValue{PSCOPF.GeneratorState}(missing,
                                                                                SortedDict("S2"=>PSCOPF.OFF, "S2"=>PSCOPF.OFF)),
                            Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.UncertainValue{PSCOPF.GeneratorState}(missing,
                                                                                SortedDict("S2"=>PSCOPF.OFF, "S2"=>PSCOPF.OFF))
                            ),
                SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.UncertainValue{Float64}(missing,
                                                                                SortedDict("S1"=>0.,"S2"=>0.)),
                            Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.UncertainValue{Float64}(missing,
                                                                                SortedDict("S1"=>0.,"S2"=>0.)),
                                                                                )
                ),
            "prod_1_2" => PSCOPF.GeneratorSchedule("prod_1_2",
                SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.UncertainValue{PSCOPF.GeneratorState}(missing,
                                                                                SortedDict("S2"=>PSCOPF.OFF, "S2"=>PSCOPF.OFF)),
                            Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.UncertainValue{PSCOPF.GeneratorState}(missing,
                                                                                SortedDict("S2"=>PSCOPF.OFF, "S2"=>PSCOPF.OFF))
                            ),
                SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.UncertainValue{Float64}(missing,
                                                                                SortedDict("S1"=>0.,"S2"=>0.)),
                            Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.UncertainValue{Float64}(missing,
                                                                                SortedDict("S1"=>0.,"S2"=>0.)),
                                                                                )
                ),
            )
        )

        tso = PSCOPF.TSOOutFO()
        result = PSCOPF.run(tso, ech, firmness,
                    PSCOPF.get_target_timepoints(context),
                    context)
        PSCOPF.update_tso_schedule!(context, ech, result, firmness, tso)

        # Solution is optimal
        @test PSCOPF.get_status(result) == PSCOPF.pscopf_OPTIMAL
        @test value(result.objective_model.start_cost) < 1e-09

        @test PSCOPF.ON == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_1", TS[1], "S1")
        @test PSCOPF.ON == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_1", TS[1], "S2")
        @test PSCOPF.ON == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_1", TS[2], "S1")
        @test PSCOPF.ON == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_1", TS[2], "S2")

        @test PSCOPF.OFF == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_2", TS[1], "S1")
        @test PSCOPF.OFF == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_2", TS[1], "S2")
        @test PSCOPF.OFF == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_2", TS[2], "S1")
        @test PSCOPF.OFF == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_2", TS[2], "S2")
    end

    #=
    prod_1_1 is OFF, prod_1_2 is ON
    We only use prod_1_2 cause its already started => no start cost => cheaper
    =#
    @testset "tso_no_starting_cost_for_unit_prod_1_2" begin
        # initial generators state
        generators_init_state = SortedDict(
            "prod_1_1" => PSCOPF.OFF,
            "prod_1_2" => PSCOPF.ON
        )
        context = PSCOPF.PSCOPFContext(network, TS, PSCOPF.PSCOPF_MODE_1,
                                        generators_init_state,
                                        uncertainties, nothing)
        context.market_schedule = PSCOPF.Schedule(PSCOPF.Market(), ech, SortedDict(
            "prod_1_1" => PSCOPF.GeneratorSchedule("prod_1_1",
                SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.UncertainValue{PSCOPF.GeneratorState}(missing,
                                                                                SortedDict("S2"=>PSCOPF.OFF, "S2"=>PSCOPF.OFF)),
                            Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.UncertainValue{PSCOPF.GeneratorState}(missing,
                                                                                SortedDict("S2"=>PSCOPF.OFF, "S2"=>PSCOPF.OFF))
                            ),
                SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.UncertainValue{Float64}(missing,
                                                                                SortedDict("S1"=>0.,"S2"=>0.)),
                            Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.UncertainValue{Float64}(missing,
                                                                                SortedDict("S1"=>0.,"S2"=>0.)),
                                                                                )
                ),
            "prod_1_2" => PSCOPF.GeneratorSchedule("prod_1_2",
                SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.UncertainValue{PSCOPF.GeneratorState}(missing,
                                                                                SortedDict("S2"=>PSCOPF.OFF, "S2"=>PSCOPF.OFF)),
                            Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.UncertainValue{PSCOPF.GeneratorState}(missing,
                                                                                SortedDict("S2"=>PSCOPF.OFF, "S2"=>PSCOPF.OFF))
                            ),
                SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.UncertainValue{Float64}(missing,
                                                                                SortedDict("S1"=>0.,"S2"=>0.)),
                            Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.UncertainValue{Float64}(missing,
                                                                                SortedDict("S1"=>0.,"S2"=>0.)),
                                                                                )
                ),
            )
        )

        tso = PSCOPF.TSOOutFO()
        result = PSCOPF.run(tso, ech, firmness,
                    PSCOPF.get_target_timepoints(context),
                    context)
        PSCOPF.update_tso_schedule!(context, ech, result, firmness, tso)

        # Solution is optimal
        @test PSCOPF.get_status(result) == PSCOPF.pscopf_OPTIMAL
        @test value(result.objective_model.start_cost) < 1e-09
        #high start cost for prod_1_1 => not used
        @test PSCOPF.OFF == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_1", TS[1], "S1")
        @test PSCOPF.OFF == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_1", TS[1], "S2")
        @test PSCOPF.OFF == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_1", TS[2], "S1")
        @test PSCOPF.OFF == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_1", TS[2], "S2")
        #no start cost for prod_1_2 => used eventhough it's unitary prop_cost is higher
        @test PSCOPF.ON == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_2", TS[1], "S1")
        @test PSCOPF.ON == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_2", TS[1], "S2")
        @test PSCOPF.ON == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_2", TS[2], "S1")
        @test PSCOPF.ON == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_2", TS[2], "S2")
    end

    #=
    prod_1_1 is OFF, prod_1_2 is OFF
    We only use prod_1_1 cause it's cheaper (mainly in terms of start cost)
    =#
    @testset "tso_starting_cost" begin
        # initial generators state
        generators_init_state = SortedDict(
            "prod_1_1" => PSCOPF.OFF,
            "prod_1_2" => PSCOPF.OFF
        )
        context = PSCOPF.PSCOPFContext(network, TS, PSCOPF.PSCOPF_MODE_1,
                                        generators_init_state,
                                        uncertainties, nothing)
        context.market_schedule = PSCOPF.Schedule(PSCOPF.Market(), ech, SortedDict(
            "prod_1_1" => PSCOPF.GeneratorSchedule("prod_1_1",
                SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.UncertainValue{PSCOPF.GeneratorState}(missing,
                                                                                SortedDict("S2"=>PSCOPF.OFF, "S2"=>PSCOPF.OFF)),
                            Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.UncertainValue{PSCOPF.GeneratorState}(missing,
                                                                                SortedDict("S2"=>PSCOPF.OFF, "S2"=>PSCOPF.OFF))
                            ),
                SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.UncertainValue{Float64}(missing,
                                                                                SortedDict("S1"=>0.,"S2"=>0.)),
                            Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.UncertainValue{Float64}(missing,
                                                                                SortedDict("S1"=>0.,"S2"=>0.)),
                                                                                )
                ),
            "prod_1_2" => PSCOPF.GeneratorSchedule("prod_1_2",
                SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.UncertainValue{PSCOPF.GeneratorState}(missing,
                                                                                SortedDict("S2"=>PSCOPF.OFF, "S2"=>PSCOPF.OFF)),
                            Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.UncertainValue{PSCOPF.GeneratorState}(missing,
                                                                                SortedDict("S2"=>PSCOPF.OFF, "S2"=>PSCOPF.OFF))
                            ),
                SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.UncertainValue{Float64}(missing,
                                                                                SortedDict("S1"=>0.,"S2"=>0.)),
                            Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.UncertainValue{Float64}(missing,
                                                                                SortedDict("S1"=>0.,"S2"=>0.)),
                                                                                )
                ),
            )
        )

        tso = PSCOPF.TSOOutFO()
        result = PSCOPF.run(tso, ech, firmness,
                    PSCOPF.get_target_timepoints(context),
                    context)
        PSCOPF.update_tso_schedule!(context, ech, result, firmness, tso)

        # Solution is optimal
        @test PSCOPF.get_status(result) == PSCOPF.pscopf_OPTIMAL
        @test 2*45000. ≈ value(result.objective_model.start_cost) # started in 2 scenarios

        #start cost for prod_1_1 prefered to prod_1_2
        @test PSCOPF.ON == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_1", TS[1], "S1")
        @test PSCOPF.ON == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_1", TS[1], "S2")
        @test PSCOPF.ON == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_1", TS[2], "S1")
        @test PSCOPF.ON == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_1", TS[2], "S2")
        #higher start cost for prod_1_2 => not used
        @test PSCOPF.OFF == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_2", TS[1], "S1")
        @test PSCOPF.OFF == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_2", TS[1], "S2")
        @test PSCOPF.OFF == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_2", TS[2], "S1")
        @test PSCOPF.OFF == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_2", TS[2], "S2")
    end

    #=
    Init : prod_1_1 is OFF, prod_1_2 is OFF
    Market : uses prod_1_2 ; started (ON) and set the prod levels satisfying the EOD
    We use prod_1_2 to follow the market, even if prod_1_1 is cheaper
    =#
    @testset "tso_starting_cost_due_to_following_market" begin
        # initial generators state
        generators_init_state = SortedDict(
            "prod_1_1" => PSCOPF.OFF,
            "prod_1_2" => PSCOPF.OFF
        )
        context = PSCOPF.PSCOPFContext(network, TS, PSCOPF.PSCOPF_MODE_1,
                                        generators_init_state,
                                        uncertainties, nothing)
        context.market_schedule = PSCOPF.Schedule(PSCOPF.Market(), ech, SortedDict(
            "prod_1_1" => PSCOPF.GeneratorSchedule("prod_1_1",
                SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.UncertainValue{PSCOPF.GeneratorState}(missing,
                                                                                SortedDict("S2"=>PSCOPF.OFF, "S2"=>PSCOPF.OFF)),
                            Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.UncertainValue{PSCOPF.GeneratorState}(missing,
                                                                                SortedDict("S2"=>PSCOPF.OFF, "S2"=>PSCOPF.OFF))
                            ),
                SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.UncertainValue{Float64}(missing,
                                                                                SortedDict("S1"=>0.,"S2"=>0.)),
                            Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.UncertainValue{Float64}(missing,
                                                                                SortedDict("S1"=>0.,"S2"=>0.)),
                                                                                )
                ),
            "prod_1_2" => PSCOPF.GeneratorSchedule("prod_1_2",
                SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.UncertainValue{PSCOPF.GeneratorState}(missing,
                                                                                SortedDict("S2"=>PSCOPF.ON, "S2"=>PSCOPF.ON)),
                            Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.UncertainValue{PSCOPF.GeneratorState}(missing,
                                                                                SortedDict("S2"=>PSCOPF.ON, "S2"=>PSCOPF.ON))
                            ),
                SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.UncertainValue{Float64}(missing,
                                                                                SortedDict("S1"=>30.,"S2"=>25.)),
                            Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.UncertainValue{Float64}(missing,
                                                                                SortedDict("S1"=>50.,"S2"=>35.)),
                                                                                )
                ),
            )
        )

        tso = PSCOPF.TSOOutFO()
        result = PSCOPF.run(tso, ech, firmness,
                    PSCOPF.get_target_timepoints(context),
                    context)
        PSCOPF.update_tso_schedule!(context, ech, result, firmness, tso)

        # Solution is optimal
        @test PSCOPF.get_status(result) == PSCOPF.pscopf_OPTIMAL
        @test 2*80000. ≈ value(result.objective_model.start_cost) # started in 2 scenarios
        @test value(result.objective_model.deltas) < 1e-09 # followed the market

        #start cost for prod_1_1 prefered to prod_1_2 but will cause high deviation from the market
        @test PSCOPF.OFF == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_1", TS[1], "S1")
        @test PSCOPF.OFF == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_1", TS[1], "S2")
        @test PSCOPF.OFF == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_1", TS[2], "S1")
        @test PSCOPF.OFF == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_1", TS[2], "S2")
        #higher start cost for prod_1_2 but we follow the market
        @test PSCOPF.ON == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_2", TS[1], "S1")
        @test PSCOPF.ON == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_2", TS[1], "S2")
        @test PSCOPF.ON == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_2", TS[2], "S1")
        @test PSCOPF.ON == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_2", TS[2], "S2")
    end

    #=
    Init : prod_1_1 is OFF, prod_1_2 is OFF
    Market : uses prod_1_2 ; started (ON) but didn't set the prod levels
    => using prod_1_1 or prod_1_2 will have the same effect on deltas
    We use prod_1_1 cause cheaper
    =#
    @testset "tso_starting_cost_deviate_from_market" begin
        # initial generators state
        generators_init_state = SortedDict(
            "prod_1_1" => PSCOPF.OFF,
            "prod_1_2" => PSCOPF.OFF
        )
        context = PSCOPF.PSCOPFContext(network, TS, PSCOPF.PSCOPF_MODE_1,
                                        generators_init_state,
                                        uncertainties, nothing)
        context.market_schedule = PSCOPF.Schedule(PSCOPF.Market(), ech, SortedDict(
            "prod_1_1" => PSCOPF.GeneratorSchedule("prod_1_1",
                SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.UncertainValue{PSCOPF.GeneratorState}(missing,
                                                                                SortedDict("S2"=>PSCOPF.OFF, "S2"=>PSCOPF.OFF)),
                            Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.UncertainValue{PSCOPF.GeneratorState}(missing,
                                                                                SortedDict("S2"=>PSCOPF.OFF, "S2"=>PSCOPF.OFF))
                            ),
                SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.UncertainValue{Float64}(missing,
                                                                                SortedDict("S1"=>0.,"S2"=>0.)),
                            Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.UncertainValue{Float64}(missing,
                                                                                SortedDict("S1"=>0.,"S2"=>0.)),
                                                                                )
                ),
            "prod_1_2" => PSCOPF.GeneratorSchedule("prod_1_2",
                SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.UncertainValue{PSCOPF.GeneratorState}(missing,
                                                                                SortedDict("S2"=>PSCOPF.ON, "S2"=>PSCOPF.ON)),
                            Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.UncertainValue{PSCOPF.GeneratorState}(missing,
                                                                                SortedDict("S2"=>PSCOPF.ON, "S2"=>PSCOPF.ON))
                            ),
                SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.UncertainValue{Float64}(missing,
                                                                                SortedDict("S1"=>0.,"S2"=>0.)),
                            Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.UncertainValue{Float64}(missing,
                                                                                SortedDict("S1"=>0.,"S2"=>0.)),
                                                                                )
                ),
            )
        )

        tso = PSCOPF.TSOOutFO()
        result = PSCOPF.run(tso, ech, firmness,
                    PSCOPF.get_target_timepoints(context),
                    context)
        PSCOPF.update_tso_schedule!(context, ech, result, firmness, tso)

        # Solution is optimal
        @test PSCOPF.get_status(result) == PSCOPF.pscopf_OPTIMAL
        @test 2*45000. ≈ value(result.objective_model.start_cost) # started in 2 scenarios
        @test (30 + 25 + 50 + 35) ≈ value(result.objective_model.deltas)

        #start cost for prod_1_1 prefered to prod_1_2
        @test PSCOPF.ON == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_1", TS[1], "S1")
        @test PSCOPF.ON == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_1", TS[1], "S2")
        @test PSCOPF.ON == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_1", TS[2], "S1")
        @test PSCOPF.ON == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_1", TS[2], "S2")
        #higher start cost for prod_1_2 => not used
        @test PSCOPF.OFF == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_2", TS[1], "S1")
        @test PSCOPF.OFF == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_2", TS[1], "S2")
        @test PSCOPF.OFF == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_2", TS[2], "S1")
        @test PSCOPF.OFF == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_2", TS[2], "S2")
    end

    #=
    Init : prod_1_1 is OFF, prod_1_2 is OFF
    Market : definitively starts prod_1_2
    => if we use prod_1_2 we do not pay for starting
    =#
    @testset "tso_no_starting_cost_for_definitive_market_starts" begin
        # initial generators state
        generators_init_state = SortedDict(
            "prod_1_1" => PSCOPF.OFF,
            "prod_1_2" => PSCOPF.OFF
        )
        context = PSCOPF.PSCOPFContext(network, TS, PSCOPF.PSCOPF_MODE_1,
                                        generators_init_state,
                                        uncertainties, nothing)
        context.market_schedule = PSCOPF.Schedule(PSCOPF.Market(), ech, SortedDict(
            "prod_1_1" => PSCOPF.GeneratorSchedule("prod_1_1",
                SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.UncertainValue{PSCOPF.GeneratorState}(missing,
                                                                                SortedDict("S2"=>PSCOPF.OFF, "S2"=>PSCOPF.OFF)),
                            Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.UncertainValue{PSCOPF.GeneratorState}(missing,
                                                                                SortedDict("S2"=>PSCOPF.OFF, "S2"=>PSCOPF.OFF))
                            ),
                SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.UncertainValue{Float64}(missing,
                                                                                SortedDict("S1"=>0.,"S2"=>0.)),
                            Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.UncertainValue{Float64}(missing,
                                                                                SortedDict("S1"=>0.,"S2"=>0.)),
                                                                                )
                ),
            "prod_1_2" => PSCOPF.GeneratorSchedule("prod_1_2",
                SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.UncertainValue{PSCOPF.GeneratorState}(PSCOPF.ON,
                                                                                SortedDict("S2"=>PSCOPF.ON, "S2"=>PSCOPF.ON)),
                            Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.UncertainValue{PSCOPF.GeneratorState}(PSCOPF.ON,
                                                                                SortedDict("S2"=>PSCOPF.ON, "S2"=>PSCOPF.ON))
                            ),
                SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.UncertainValue{Float64}(missing,
                                                                                SortedDict("S1"=>0.,"S2"=>0.)),
                            Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.UncertainValue{Float64}(missing,
                                                                                SortedDict("S1"=>0.,"S2"=>0.)),
                                                                                )
                ),
            )
        )

        tso = PSCOPF.TSOOutFO()
        result = PSCOPF.run(tso, ech, firmness,
                    PSCOPF.get_target_timepoints(context),
                    context)
        PSCOPF.update_tso_schedule!(context, ech, result, firmness, tso)

        # Solution is optimal
        @test PSCOPF.get_status(result) == PSCOPF.pscopf_OPTIMAL
        @test value(result.objective_model.start_cost) <= 1e-09 # starting cost was paid by market

        #start cost for prod_1_1 prefered to prod_1_2
        @test PSCOPF.OFF == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_1", TS[1], "S1")
        @test PSCOPF.OFF == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_1", TS[1], "S2")
        @test PSCOPF.OFF == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_1", TS[2], "S1")
        @test PSCOPF.OFF == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_1", TS[2], "S2")
        #higher start cost for prod_1_2 => not used
        @test PSCOPF.ON == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_2", TS[1], "S1")
        @test PSCOPF.ON == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_2", TS[1], "S2")
        @test PSCOPF.ON == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_2", TS[2], "S1")
        @test PSCOPF.ON == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_2", TS[2], "S2")
    end

    #=
    Init : prod_1_1 is OFF, prod_1_2 is OFF
    TSO already definitively started prod_1_2
    => if we use prod_1_2 we do not pay for starting
    =#
    @testset "tso_no_starting_cost_for_past_definitive_tso_starts" begin
        # initial generators state
        generators_init_state = SortedDict(
            "prod_1_1" => PSCOPF.OFF,
            "prod_1_2" => PSCOPF.OFF
        )
        context = PSCOPF.PSCOPFContext(network, TS, PSCOPF.PSCOPF_MODE_1,
                                        generators_init_state,
                                        uncertainties, nothing)
        context.market_schedule = PSCOPF.Schedule(PSCOPF.Market(), ech, SortedDict(
            "prod_1_1" => PSCOPF.GeneratorSchedule("prod_1_1",
                SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.UncertainValue{PSCOPF.GeneratorState}(missing,
                                                                                SortedDict("S2"=>PSCOPF.OFF, "S2"=>PSCOPF.OFF)),
                            Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.UncertainValue{PSCOPF.GeneratorState}(missing,
                                                                                SortedDict("S2"=>PSCOPF.OFF, "S2"=>PSCOPF.OFF))
                            ),
                SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.UncertainValue{Float64}(missing,
                                                                                SortedDict("S1"=>0.,"S2"=>0.)),
                            Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.UncertainValue{Float64}(missing,
                                                                                SortedDict("S1"=>0.,"S2"=>0.)),
                                                                                )
                ),
            "prod_1_2" => PSCOPF.GeneratorSchedule("prod_1_2",
                SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.UncertainValue{PSCOPF.GeneratorState}(PSCOPF.OFF,
                                                                                SortedDict("S2"=>PSCOPF.OFF, "S2"=>PSCOPF.OFF)),
                            Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.UncertainValue{PSCOPF.GeneratorState}(PSCOPF.OFF,
                                                                                SortedDict("S2"=>PSCOPF.OFF, "S2"=>PSCOPF.OFF))
                            ),
                SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.UncertainValue{Float64}(missing,
                                                                                SortedDict("S1"=>0.,"S2"=>0.)),
                            Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.UncertainValue{Float64}(missing,
                                                                                SortedDict("S1"=>0.,"S2"=>0.)),
                                                                                )
                ),
            )
        )
        context.tso_schedule = PSCOPF.Schedule(PSCOPF.Market(), ech, SortedDict(
            "prod_1_1" => PSCOPF.GeneratorSchedule("prod_1_1",
                SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.UncertainValue{PSCOPF.GeneratorState}(missing,
                                                                                SortedDict("S2"=>PSCOPF.OFF, "S2"=>PSCOPF.OFF)),
                            Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.UncertainValue{PSCOPF.GeneratorState}(missing,
                                                                                SortedDict("S2"=>PSCOPF.OFF, "S2"=>PSCOPF.OFF))
                            ),
                SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.UncertainValue{Float64}(missing,
                                                                                SortedDict("S1"=>0.,"S2"=>0.)),
                            Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.UncertainValue{Float64}(missing,
                                                                                SortedDict("S1"=>0.,"S2"=>0.)),
                                                                                )
                ),
            "prod_1_2" => PSCOPF.GeneratorSchedule("prod_1_2",
                SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.UncertainValue{PSCOPF.GeneratorState}(PSCOPF.ON,
                                                                                SortedDict("S2"=>PSCOPF.ON, "S2"=>PSCOPF.ON)),
                            Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.UncertainValue{PSCOPF.GeneratorState}(PSCOPF.ON,
                                                                                SortedDict("S2"=>PSCOPF.ON, "S2"=>PSCOPF.ON))
                            ),
                SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.UncertainValue{Float64}(missing,
                                                                                SortedDict("S1"=>0.,"S2"=>0.)),
                            Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.UncertainValue{Float64}(missing,
                                                                                SortedDict("S1"=>0.,"S2"=>0.)),
                                                                                )
                ),
            )
        )
        firmness_l = PSCOPF.Firmness(
                SortedDict("prod_1_1" => SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.FREE,
                                                    Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.FREE),
                            "prod_1_2" => SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.DECIDED,
                                                    Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.DECIDED) ),
                SortedDict( "prod_1_1" => SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.FREE,
                                                    Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.FREE),
                            "prod_1_2" => SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.FREE,
                                                    Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.FREE), )
                )

        tso = PSCOPF.TSOOutFO()
        result = PSCOPF.run(tso, ech, firmness_l,
                    PSCOPF.get_target_timepoints(context),
                    context)
        PSCOPF.update_tso_schedule!(context, ech, result, firmness_l, tso)

        # Solution is optimal
        @test PSCOPF.get_status(result) == PSCOPF.pscopf_OPTIMAL
        @test value(result.objective_model.start_cost) <= 1e-09 # starting cost was paid by market

        #start cost for prod_1_1 prefered to prod_1_2
        @test PSCOPF.OFF == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_1", TS[1], "S1")
        @test PSCOPF.OFF == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_1", TS[1], "S2")
        @test PSCOPF.OFF == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_1", TS[2], "S1")
        @test PSCOPF.OFF == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_1", TS[2], "S2")
        #higher start cost for prod_1_2 => not used
        @test PSCOPF.ON == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_2", TS[1], "S1")
        @test PSCOPF.ON == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_2", TS[1], "S2")
        @test PSCOPF.ON == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_2", TS[2], "S1")
        @test PSCOPF.ON == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_2", TS[2], "S2")
    end

    #=
    TS: [11h, 11h15]
    S: [S1,S2]
                        bus 1
                        |
    (pilotable) prod_1_1|          load_1
    Pmin=10, Pmax=100   |S1: 30     S1: 50
    Csta=45k, Cprop=10  |S2: 25     S2: 165
                        |
    (pilotable) prod_1_2|
     Pmin=10, Pmax=100  |
     Csta=80k, Cprop=15 |

    We have high demand for TS2, S2
    =#

    #=
    both units are ON
    In TS2,S2, we need both units
    but in TS1 one unit is enough
    => if we turn off a unit in TS1, we will need to restart it for TS2
    => it's cheaper to use both units at TS1 non-optimally to get the global optimum
    =#
    @testset "tso_both_units_used_in_S2_to_avoid_restart_cost" begin
        #bus2, S2, TS:11h15 : high demand
        PSCOPF.add_uncertainty!(uncertainties, ech, "bus_1", DateTime("2015-01-01T11:15:00"), "S2", 165.)

        # initial generators state
        generators_init_state = SortedDict(
            "prod_1_1" => PSCOPF.ON,
            "prod_1_2" => PSCOPF.ON
        )
        context = PSCOPF.PSCOPFContext(network, TS, PSCOPF.PSCOPF_MODE_1,
                                        generators_init_state,
                                        uncertainties, nothing)

        context.market_schedule = PSCOPF.Schedule(PSCOPF.Market(), ech, SortedDict(
            "prod_1_1" => PSCOPF.GeneratorSchedule("prod_1_1",
                SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.UncertainValue{PSCOPF.GeneratorState}(missing,
                                                                                SortedDict("S2"=>PSCOPF.OFF, "S2"=>PSCOPF.OFF)),
                            Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.UncertainValue{PSCOPF.GeneratorState}(missing,
                                                                                SortedDict("S2"=>PSCOPF.OFF, "S2"=>PSCOPF.OFF))
                            ),
                SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.UncertainValue{Float64}(missing,
                                                                                SortedDict("S1"=>0.,"S2"=>0.)),
                            Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.UncertainValue{Float64}(missing,
                                                                                SortedDict("S1"=>0.,"S2"=>0.)),
                                                                                )
                ),
            "prod_1_2" => PSCOPF.GeneratorSchedule("prod_1_2",
                SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.UncertainValue{PSCOPF.GeneratorState}(missing,
                                                                                SortedDict("S2"=>PSCOPF.OFF, "S2"=>PSCOPF.OFF)),
                            Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.UncertainValue{PSCOPF.GeneratorState}(missing,
                                                                                SortedDict("S2"=>PSCOPF.OFF, "S2"=>PSCOPF.OFF))
                            ),
                SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.UncertainValue{Float64}(missing,
                                                                                SortedDict("S1"=>0.,"S2"=>0.)),
                            Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.UncertainValue{Float64}(missing,
                                                                                SortedDict("S1"=>0.,"S2"=>0.)),
                                                                                )
                ),
            )
        )

        tso = PSCOPF.TSOOutFO()
        result = PSCOPF.run(tso, ech, firmness,
                    PSCOPF.get_target_timepoints(context),
                    context)
        PSCOPF.update_tso_schedule!(context, ech, result, firmness, tso)

        # Solution is optimal
        @test PSCOPF.get_status(result) == PSCOPF.pscopf_OPTIMAL
        @test value(result.objective_model.start_cost) <= 1e-09 # No start cost, in S2, prod_1_2 is used in TS1 to avoid restart

        #start cost for prod_1_1 prefered to prod_1_2
        @test 30. ≈ PSCOPF.get_prod_value(context.tso_schedule, "prod_1_1", TS[1], "S1")
        @test PSCOPF.ON == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_1", TS[1], "S1")
        @test 15. ≈ PSCOPF.get_prod_value(context.tso_schedule, "prod_1_1", TS[1], "S2")
        @test PSCOPF.ON == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_1", TS[1], "S2")
        @test 50. ≈ PSCOPF.get_prod_value(context.tso_schedule, "prod_1_1", TS[2], "S1")
        @test PSCOPF.ON == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_1", TS[2], "S1")
        @test 100. ≈ PSCOPF.get_prod_value(context.tso_schedule, "prod_1_1", TS[2], "S2")
        @test PSCOPF.ON == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_1", TS[2], "S2")
        #higher start cost for prod_1_2 => not used
        @test PSCOPF.OFF == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_2", TS[1], "S1")
        @test 10. ≈ PSCOPF.get_prod_value(context.tso_schedule, "prod_1_2", TS[1], "S2")
        @test PSCOPF.ON == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_2", TS[1], "S2")
        @test PSCOPF.OFF == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_2", TS[2], "S1")
        @test 65. ≈ PSCOPF.get_prod_value(context.tso_schedule, "prod_1_2", TS[2], "S2")
        @test PSCOPF.ON == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_2", TS[2], "S2")

        #reset original value
        PSCOPF.add_uncertainty!(uncertainties, ech, "bus_1", DateTime("2015-01-01T11:15:00"), "S2", 65.)
    end

    #=
    both units are OFF
    in TS1 one unit is enough => start prod_1_1
    In TS2,S2, we need both units => start prod_1_2
    =#
    @testset "tso_both_units_are_started_when_needed" begin
        #bus2, S2, TS:11h15 : high demand
        PSCOPF.add_uncertainty!(uncertainties, ech, "bus_1", DateTime("2015-01-01T11:15:00"), "S2", 165.)

        # initial generators state
        generators_init_state = SortedDict(
            "prod_1_1" => PSCOPF.OFF,
            "prod_1_2" => PSCOPF.OFF
        )
        context = PSCOPF.PSCOPFContext(network, TS, PSCOPF.PSCOPF_MODE_1,
                                        generators_init_state,
                                        uncertainties, nothing)
        context.market_schedule = PSCOPF.Schedule(PSCOPF.Market(), ech, SortedDict(
            "prod_1_1" => PSCOPF.GeneratorSchedule("prod_1_1",
                SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.UncertainValue{PSCOPF.GeneratorState}(missing,
                                                                                SortedDict("S2"=>PSCOPF.OFF, "S2"=>PSCOPF.OFF)),
                            Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.UncertainValue{PSCOPF.GeneratorState}(missing,
                                                                                SortedDict("S2"=>PSCOPF.OFF, "S2"=>PSCOPF.OFF))
                            ),
                SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.UncertainValue{Float64}(missing,
                                                                                SortedDict("S1"=>0.,"S2"=>0.)),
                            Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.UncertainValue{Float64}(missing,
                                                                                SortedDict("S1"=>0.,"S2"=>0.)),
                                                                                )
                ),
            "prod_1_2" => PSCOPF.GeneratorSchedule("prod_1_2",
                SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.UncertainValue{PSCOPF.GeneratorState}(missing,
                                                                                SortedDict("S2"=>PSCOPF.OFF, "S2"=>PSCOPF.OFF)),
                            Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.UncertainValue{PSCOPF.GeneratorState}(missing,
                                                                                SortedDict("S2"=>PSCOPF.OFF, "S2"=>PSCOPF.OFF))
                            ),
                SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.UncertainValue{Float64}(missing,
                                                                                SortedDict("S1"=>0.,"S2"=>0.)),
                            Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.UncertainValue{Float64}(missing,
                                                                                SortedDict("S1"=>0.,"S2"=>0.)),
                                                                                )
                ),
            )
        )

        tso = PSCOPF.TSOOutFO()
        result = PSCOPF.run(tso, ech, firmness,
                    PSCOPF.get_target_timepoints(context),
                    context)
        PSCOPF.update_tso_schedule!(context, ech, result, firmness, tso)

        # Solution is optimal
        @test PSCOPF.get_status(result) == PSCOPF.pscopf_OPTIMAL
        @test (2*45000 + 80000) ≈ value(result.objective_model.start_cost)  # prod1 started in 2 scenarios, prod2 in S2

        # S1 : prod_1_1 is cheaper to start => used
        @test PSCOPF.ON == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_1", TS[1], "S1")
        @test PSCOPF.OFF == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_2", TS[1], "S1")
        @test PSCOPF.ON == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_1", TS[2], "S1")
        @test PSCOPF.OFF == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_2", TS[2], "S1")
        @test 30. ≈ PSCOPF.get_prod_value(context.tso_schedule, "prod_1_1", TS[1], "S1")
        @test 50. ≈ PSCOPF.get_prod_value(context.tso_schedule, "prod_1_1", TS[2], "S1")
        @test PSCOPF.get_prod_value(context.tso_schedule, "prod_1_2", TS[1], "S1") < 1e-09
        @test PSCOPF.get_prod_value(context.tso_schedule, "prod_1_2", TS[2], "S1") < 1e-09

        # S2 : prod_1_2 is needed to satisfy demand it also has high proportional cost
        # => prod_1_2 is only started when needed
        @test PSCOPF.ON == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_1", TS[1], "S2")
        @test PSCOPF.ON == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_1", TS[2], "S2")
        @test PSCOPF.OFF == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_2", TS[1], "S2")
        @test PSCOPF.ON == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_2", TS[2], "S2")
        @test 25. ≈ PSCOPF.get_prod_value(context.tso_schedule, "prod_1_1", TS[1], "S2")
        @test 100. ≈ PSCOPF.get_prod_value(context.tso_schedule, "prod_1_1", TS[2], "S2")
        @test PSCOPF.get_prod_value(context.tso_schedule, "prod_1_2", TS[1], "S2") < 1e-09
        @test 65. ≈ PSCOPF.get_prod_value(context.tso_schedule, "prod_1_2", TS[2], "S2")

        #reset original value
        PSCOPF.add_uncertainty!(uncertainties, ech, "bus_1", DateTime("2015-01-01T11:15:00"), "S2", 65.)
    end

    #=
    definitive starting decisions made in
     the tso_schedule and the reference market schedule are gratis for the TSO
    Here, in tso_schedule, we :
        - consider starting prod_1_1  (non-definitive decision) => it is not gratis for market
        - start prod_1_2 (definitive decision) => prod_1_2 has no start cost for market
    =#
    @testset "tso_test_gratis_start" begin
        #bus2, S2, TS:11h15 : high demand
        PSCOPF.add_uncertainty!(uncertainties, ech, "bus_1", DateTime("2015-01-01T11:15:00"), "S2", 165.)

        # initial generators state
        generators_init_state = SortedDict(
            "prod_1_1" => PSCOPF.OFF,
            "prod_1_2" => PSCOPF.OFF
        )
        context = PSCOPF.PSCOPFContext(network, TS, PSCOPF.PSCOPF_MODE_1,
                                        generators_init_state,
                                        uncertainties, nothing)

        context.market_schedule = PSCOPF.Schedule(PSCOPF.TSO(), Dates.DateTime("2015-01-01T06:00:00"), SortedDict(
                                        "prod_1_1" => PSCOPF.GeneratorSchedule("prod_1_1",
                                            SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.UncertainValue{PSCOPF.GeneratorState}(missing,
                                                                                SortedDict("S1"=>PSCOPF.ON, "S2"=>PSCOPF.ON)),
                                                        Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.UncertainValue{PSCOPF.GeneratorState}(missing,
                                                                                SortedDict("S1"=>PSCOPF.ON, "S2"=>PSCOPF.ON))
                                                        ),
                                            SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.UncertainValue{Float64}(missing,
                                                                                SortedDict("S1"=>0.,"S2"=>0.)),
                                                        Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.UncertainValue{Float64}(missing,
                                                                                SortedDict("S1"=>0.,"S2"=>0.)),
                                                                                )
                                            ),
                                        "prod_1_2" => PSCOPF.GeneratorSchedule("prod_1_2",
                                            SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.UncertainValue{PSCOPF.GeneratorState}(PSCOPF.ON,
                                                                                SortedDict("S1"=>PSCOPF.ON, "S2"=>PSCOPF.ON)),
                                                        Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.UncertainValue{PSCOPF.GeneratorState}(PSCOPF.ON,
                                                                                SortedDict("S1"=>PSCOPF.ON, "S2"=>PSCOPF.ON))
                                                        ),
                                            SortedDict(Dates.DateTime("2015-01-01T11:00:00") => PSCOPF.UncertainValue{Float64}(missing,
                                                                                SortedDict("S1"=>0.,"S2"=>0.)),
                                                        Dates.DateTime("2015-01-01T11:15:00") => PSCOPF.UncertainValue{Float64}(missing,
                                                                                SortedDict("S1"=>0.,"S2"=>0.)),
                                                                                )
                                            )
                                        )
                                )

        tso = PSCOPF.TSOOutFO()
        result = PSCOPF.run(tso, ech, firmness,
                    PSCOPF.get_target_timepoints(context),
                    context)
        PSCOPF.update_tso_schedule!(context, ech, result, firmness, tso)

        # Solution is optimal
        @test PSCOPF.get_status(result) == PSCOPF.pscopf_OPTIMAL
        @test (45000) ≈ value(result.objective_model.start_cost)  # prod1 started in 2 scenarios, prod2 in S2
        @test( value(result.objective_model.prop_cost) ≈
              (   (0.   *10. + 30. * 15) #TS1, S1
                + (0.   *10. + 50. * 15) #TS2, S1
                + (15.  *10. + 10.  * 15) #TS1, S2
                + (100. *10. + 65. * 15) #TS2, S2
              )
        )

        #S1 : need one generator, prod_1_2 can be started gratis => no need to start prod_1_1
        @test PSCOPF.OFF == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_1", TS[1], "S1")
        @test PSCOPF.ON == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_2", TS[1], "S1")
        @test PSCOPF.OFF == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_1", TS[2], "S1")
        @test PSCOPF.ON == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_2", TS[2], "S1")
        @test PSCOPF.get_prod_value(context.tso_schedule, "prod_1_1", TS[1], "S1") < 1e-09
        @test PSCOPF.get_prod_value(context.tso_schedule, "prod_1_1", TS[2], "S1") < 1e-09
        @test 30. ≈ PSCOPF.get_prod_value(context.tso_schedule, "prod_1_2", TS[1], "S1")
        @test 50. ≈ PSCOPF.get_prod_value(context.tso_schedule, "prod_1_2", TS[2], "S1")
        #S2 : need both generators at TS2, prod_1_2 can be started gratis at TS1
        # => we use prod_1_2 since ts1, to get its free starting (starting it at TS2 would cost less prop_cost but <e would pay for starting)
        # prod_1_1 is use right from TS1 even if not really needed cause we will pay for starting anyways, so at least use it at TS1 to reduce prop_cost
        @test PSCOPF.ON == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_1", TS[1], "S2")
        @test PSCOPF.ON == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_2", TS[1], "S2")
        @test PSCOPF.ON == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_1", TS[2], "S2")
        @test PSCOPF.ON == PSCOPF.get_commitment_value(context.tso_schedule, "prod_1_2", TS[2], "S2")
        @test 15. ≈ PSCOPF.get_prod_value(context.tso_schedule, "prod_1_1", TS[1], "S2")
        @test 10. ≈ PSCOPF.get_prod_value(context.tso_schedule, "prod_1_2", TS[1], "S2")
        @test 100. ≈ PSCOPF.get_prod_value(context.tso_schedule, "prod_1_1", TS[2], "S2")
        @test 65. ≈ PSCOPF.get_prod_value(context.tso_schedule, "prod_1_2", TS[2], "S2")

        #reset original value
        PSCOPF.add_uncertainty!(uncertainties, ech, "bus_1", DateTime("2015-01-01T11:15:00"), "S2", 65.)
    end

end

max_stars_repo_name: klorel/pscopf

max_issues_repo_name: klorel/pscopf

max_forks_repo_name: klorel/pscopf


text:
using SpgpGpu
using Statistics

x = parse.(Float64, readlines("test_data/train_inputs"))
y = parse.(Float64, readlines("test_data/train_outputs"))
xt = parse.(Float64, readlines("test_data/test_inputs"))
me_y = mean(y);
y0 = y .- me_y

n_observ, n_dim = size(x, 1), size(x, 2)

hyp = [1.2106, -0.51968, -2.7338]
xb = [1.1462, 0.11905, 5.3042, 1.441, 3.1853,
    2.89, 1.4412, 1.4414, 1.1465, 5.6914,
    3.1857, 1.6291, 1.4429, 1.6265, 4.3954,
    1.4429, 1.4428, 1.4416, 3.3293, 1.6156]

# PREDICTION
μ0, σ² = predict(y0, x, xb, xt, hyp)
μ = μ0 .+ me_y
σ² = σ² .+ exp(hyp[end])

ext: jl


text:
@testset "LWR" begin
  # 1D regression
  Random.seed!(2017)
  N = 100
  x = range(0, stop=1, length=N)
  y = x.^2 .+ [i/1000*randn() for i=1:N]

  sdata   = georef((y=y,), reshape(x, 1, length(x)))
  sdomain = CartesianGrid((0.,), (1.,), dims=(N,))
  problem = EstimationProblem(sdata, sdomain, :y)

  solver = LWR(:y => (neighbors=10,))

  solution = solve(problem, solver)

  yhat = solution[:y]
  yvar = solution[:y_variance]

  if visualtests
    plt = scatter(x, y, label="data", size=(1000,400))
    plot!(x, yhat, ribbon=yvar, fillalpha=.5, label="LWR")
    @test_reference "data/lwr-1D.png" plt
  end

  # 2D regression
  sdata   = georef((y=[1.,0.,1.,0.],), [25. 50. 75. 75.;  25. 75. 50. 25.])
  sdomain = CartesianGrid(100,100)
  problem = EstimationProblem(sdata, sdomain, :y)

  solver₃ = LWR(:y => (neighbors=3,))
  solver₄ = LWR(:y => (neighbors=4,))

  sol₃ = solve(problem, solver₃)
  sol₄ = solve(problem, solver₄)

  if visualtests
    plt = contourf(sol₃, size=(800,400))
    plot!(sdata)
    @test_reference "data/lwr-3neigh.png" plt

    plt = contourf(sol₄, size=(800,400))
    plot!(sdata)
    @test_reference "data/lwr-4neigh.png" plt
  end

  # Haversine distance
  x = [81.45,81.42,83.35,85.24,89.51,91.01,93.05,93.07,96.04,94.09,95.03,310.0,31.0,324.0,301.0,285.0,12.0,359.0,90.0,67.0,236.0,284.0,271.0,292.0,123.0,225.0,107.0,237.0,126.0,126.0,127.5,129.0,129.0,12.56,13.23,10.23,303.0,307.0,314.0,301.0,305.0,312.0,322.0,194.0,192.0,191.0,177.0,352.99,349.75,343.61,342.84,341.85,342.83,341.43,338.93,346.0,341.7,335.5,342.5,9.0,358.55,355.75,351.45,347.79,346.92,344.42,342.73,343.7,342.0,347.65,155.39,153.29,154.08,153.45,152.0,152.2,139.17,310.44,310.52,312.0,309.93,310.47,352.41,314.85,299.72,12.36,255.64,214.41,121.32,130.58,254.7,278.76,103.07,269.0,92.0,110.0,119.0,354.0,342.0,350.0,110.0,228.3,276.1,264.4,161.4,240.82,302.0,130.13,107.11,106.43,88.57,353.0,341.5,337.92,344.94,246.4,286.27,300.36,299.65,300.63,300.42,311.6,332.0,338.0,353.0,332.0,5.25,352.0,6.8,346.01,326.0,333.0,284.0,288.0,106.4,49.2,182.13,182.7,183.18,277.0,73.3,60.0,28.0,23.0,136.0,43.0,124.2,309.5,286.45,286.6,289.76,358.0,132.65,115.63,115.4,119.0,145.05,275.0,265.0,262.66,149.3,44.0,49.0,150.0,290.0,288.0,168.0,241.0,226.0,34.5,301.0,289.7,121.9,288.0,326.0,332.0,333.0,160.0,280.0,85.0,33.0,127.0,140.0,170.0,150.0,106.0,122.0,298.0,69.0,180.0,147.0,43.0,40.0,140.0,145.0,83.0,171.0,115.0,25.0,323.0,55.5,356.8,352.2,351.3,349.5,346.25,351.5,35.0,5.0,12.0,36.0,36.2,41.0,42.5,48.0,43.15,314.58,304.68,324.95,319.5,287.88,300.0,8.0,132.0,132.0,128.0,146.15,172.4,289.1,38.85,32.6,32.87,18.45,13.28,11.3,2.9,280.0,240.0,170.0,120.0,60.0,20.0,320.0,37.0]
  y = [9.39,11.32,14.42,16.38,18.28,18.21,17.11,14.3,12.46,11.45,10.12,0.0,32.0,-11.0,-34.5,11.2,-6.0,53.0,21.0,24.0,46.3,40.3,30.0,49.0,31.0,70.0,9.0,37.5,37.0,35.0,34.5,35.0,37.0,-29.42,-25.36,-14.53,64.0,62.0,60.0,59.0,55.0,59.5,60.0,54.0,54.0,54.0,53.0,34.2,32.24,25.02,23.44,21.2,19.29,13.5,4.55,28.0,17.0,12.5,8.0,42.0,34.41,31.58,28.59,22.44,20.32,18.09,14.38,14.08,14.0,11.43,50.51,48.59,49.56,48.23,47.0,-31.5,-35.1,65.07,65.1,7.5,65.1,65.08,45.04,43.29,-52.55,-12.12,18.13,54.37,2.42,36.39,19.06,-7.35,-7.21,13.0,5.0,7.0,23.0,3.0,14.0,36.0,35.0,46.3,-3.6,4.1,46.3,47.09,-35.0,-9.06,-8.25,-8.7,-6.02,62.0,63.4,60.57,46.3,27.0,-16.92,14.72,13.03,12.55,11.03,3.4,69.0,65.0,62.0,78.0,60.5,71.0,65.0,72.9,67.0,72.3,-46.2,-38.0,-7.9,-67.37,-18.57,-18.5,-20.13,3.0,0.4,-10.0,-32.0,-33.0,-36.0,15.0,-8.3,13.25,-49.75,-52.33,-54.95,58.0,-1.2,-33.32,-34.42,-20.0,-4.08,10.0,13.0,19.83,-5.0,-12.0,-19.0,-5.0,-65.0,-66.0,-15.0,37.62,53.0,-20.0,-63.5,-23.85,20.25,-42.0,65.0,70.5,71.0,-10.0,13.0,-59.0,-27.0,-8.0,35.0,54.0,10.0,-6.0,-10.0,16.0,-49.0,-80.0,-43.0,12.0,20.0,27.0,15.0,-65.0,-45.0,25.0,67.0,66.0,-21.0,36.1,36.8,35.8,45.4,56.5,47.5,26.0,43.0,45.0,37.0,37.0,17.5,11.6,12.0,11.6,-60.95,-66.05,-74.4,-77.15,69.0,66.5,68.0,-4.0,-7.0,-9.0,-17.3,-41.0,-69.5,-23.37,-31.5,-29.63,-35.78,-30.58,-25.5,-50.58,-70.0,-70.0,-70.0,-65.0,-65.0,-70.0,-75.0,25.0]
  z = [-16.2,-16.1,-13.9,-15.1,-12.6,-12.8,-8.6,-9.3,-11.3,-9.2,-10.0,-9.2,-3.3,-12.9,-10.3,-8.3,-16.1,-13.5,-15.7,-12.2,-6.6,-11.3,-10.9,-5.3,-10.7,-14.3,-9.5,-3.6,-16.9,-13.2,-18.4,-13.4,-23.9,-11.0,-12.2,-18.8,-27.5,-24.1,-16.5,-17.0,-27.7,-8.8,-7.9,7.4,7.6,7.2,8.5,-11.7,-13.0,-15.3,-14.4,-14.8,-14.3,-15.7,-18.6,-12.8,-14.6,-12.0,-14.4,-13.0,-11.8,-13.6,-17.1,-17.8,-17.9,-17.1,-16.2,-14.3,-14.5,-12.1,7.6,7.3,7.7,9.6,10.1,-1.3,-6.0,-33.0,-24.2,-10.2,-34.3,-26.1,-9.9,-24.5,-4.4,-24.9,4.2,1.6,4.2,-9.3,1.3,-5.0,-13.8,2.0,-13.8,-10.6,-9.6,-9.1,-13.6,-8.5,-9.7,-6.1,-4.3,-3.4,-0.5,-6.0,-9.0,-9.3,-3.1,-2.8,-7.7,-6.5,7.6,3.4,-11.6,7.6,-3.2,-9.1,-9.6,-11.6,-13.3,-10.7,4.0,8.0,7.0,-13.0,-14.0,5.0,-14.0,-13.7,-38.0,-29.8,5.6,2.5,-2.6,-38.2,9.2,8.8,7.5,9.8,6.4,7.3,-9.2,-9.5,-21.1,4.1,-1.9,-12.0,-1.4,1.1,9.8,-10.6,4.1,1.7,-4.6,-15.1,7.1,7.3,5.9,1.5,8.3,3.4,-22.1,8.3,1.1,-3.2,7.4,-0.5,9.6,-14.5,5.7,5.5,-2.8,2.1,-38.0,4.0,-29.8,1.8,4.1,-0.5,-30.0,-12.4,7.7,7.4,7.4,0.0,-3.3,5.5,0.0,-20.8,-5.2,5.0,5.0,2.3,6.7,-8.0,-5.3,-10.2,-19.4,-26.0,4.0,-10.1,-8.4,-11.8,-11.6,-11.1,-11.6,-9.1,-9.7,-10.8,-6.2,-6.3,-4.2,6.2,10.0,6.3,-6.2,-5.7,-7.7,-10.4,-23.1,-25.8,-15.1,-8.7,-8.1,-9.5,-6.3,1.2,5.5,-15.5,-13.4,-15.3,-11.2,-11.6,-11.6,-4.8,-4.1,-3.7,-6.8,-14.3,-18.8,-14.0,-8.0,-11.2]
  sdata   = georef((z=z,), [x y]')
  sdomain = begin
    dims = (180, 91)
    start = (1.0, -89.01098901098901)
    finish = (359.0, 89.01098901098901)
    CartesianGrid(start, finish, dims=dims)
  end
  problem = EstimationProblem(sdata, sdomain, :z)

  solver = LWR(:z => (distance=Haversine(6371.),neighbors=49))

  solution = solve(problem, solver)

  if visualtests
    @test_reference "data/lwr-haversine.png" contourf(solution, size=(900,250))
  end
end


text:
# This file was generated by the Julia Swagger Code Generator
# Do not modify this file directly. Modify the swagger specification instead.

mutable struct ApplicationGatewayBackendHttpSettingsPropertiesFormat <: SwaggerModel
    port::Any # spec type: Union{ Nothing, Int32 } # spec name: port
    protocol::Any # spec type: Union{ Nothing, ApplicationGatewayProtocol } # spec name: protocol
    cookieBasedAffinity::Any # spec type: Union{ Nothing, String } # spec name: cookieBasedAffinity
    requestTimeout::Any # spec type: Union{ Nothing, Int32 } # spec name: requestTimeout
    probe::Any # spec type: Union{ Nothing, SubResource } # spec name: probe
    authenticationCertificates::Any # spec type: Union{ Nothing, Vector{SubResource} } # spec name: authenticationCertificates
    trustedRootCertificates::Any # spec type: Union{ Nothing, Vector{SubResource} } # spec name: trustedRootCertificates
    connectionDraining::Any # spec type: Union{ Nothing, ApplicationGatewayConnectionDraining } # spec name: connectionDraining
    hostName::Any # spec type: Union{ Nothing, String } # spec name: hostName
    pickHostNameFromBackendAddress::Any # spec type: Union{ Nothing, Bool } # spec name: pickHostNameFromBackendAddress
    affinityCookieName::Any # spec type: Union{ Nothing, String } # spec name: affinityCookieName
    probeEnabled::Any # spec type: Union{ Nothing, Bool } # spec name: probeEnabled
    path::Any # spec type: Union{ Nothing, String } # spec name: path
    provisioningState::Any # spec type: Union{ Nothing, ProvisioningState } # spec name: provisioningState

    function ApplicationGatewayBackendHttpSettingsPropertiesFormat(;port=nothing, protocol=nothing, cookieBasedAffinity=nothing, requestTimeout=nothing, probe=nothing, authenticationCertificates=nothing, trustedRootCertificates=nothing, connectionDraining=nothing, hostName=nothing, pickHostNameFromBackendAddress=nothing, affinityCookieName=nothing, probeEnabled=nothing, path=nothing, provisioningState=nothing)
        o = new()
        validate_property(ApplicationGatewayBackendHttpSettingsPropertiesFormat, Symbol("port"), port)
        setfield!(o, Symbol("port"), port)
        validate_property(ApplicationGatewayBackendHttpSettingsPropertiesFormat, Symbol("protocol"), protocol)
        setfield!(o, Symbol("protocol"), protocol)
        validate_property(ApplicationGatewayBackendHttpSettingsPropertiesFormat, Symbol("cookieBasedAffinity"), cookieBasedAffinity)
        setfield!(o, Symbol("cookieBasedAffinity"), cookieBasedAffinity)
        validate_property(ApplicationGatewayBackendHttpSettingsPropertiesFormat, Symbol("requestTimeout"), requestTimeout)
        setfield!(o, Symbol("requestTimeout"), requestTimeout)
        validate_property(ApplicationGatewayBackendHttpSettingsPropertiesFormat, Symbol("probe"), probe)
        setfield!(o, Symbol("probe"), probe)
        validate_property(ApplicationGatewayBackendHttpSettingsPropertiesFormat, Symbol("authenticationCertificates"), authenticationCertificates)
        setfield!(o, Symbol("authenticationCertificates"), authenticationCertificates)
        validate_property(ApplicationGatewayBackendHttpSettingsPropertiesFormat, Symbol("trustedRootCertificates"), trustedRootCertificates)
        setfield!(o, Symbol("trustedRootCertificates"), trustedRootCertificates)
        validate_property(ApplicationGatewayBackendHttpSettingsPropertiesFormat, Symbol("connectionDraining"), connectionDraining)
        setfield!(o, Symbol("connectionDraining"), connectionDraining)
        validate_property(ApplicationGatewayBackendHttpSettingsPropertiesFormat, Symbol("hostName"), hostName)
        setfield!(o, Symbol("hostName"), hostName)
        validate_property(ApplicationGatewayBackendHttpSettingsPropertiesFormat, Symbol("pickHostNameFromBackendAddress"), pickHostNameFromBackendAddress)
        setfield!(o, Symbol("pickHostNameFromBackendAddress"), pickHostNameFromBackendAddress)
        validate_property(ApplicationGatewayBackendHttpSettingsPropertiesFormat, Symbol("affinityCookieName"), affinityCookieName)
        setfield!(o, Symbol("affinityCookieName"), affinityCookieName)
        validate_property(ApplicationGatewayBackendHttpSettingsPropertiesFormat, Symbol("probeEnabled"), probeEnabled)
        setfield!(o, Symbol("probeEnabled"), probeEnabled)
        validate_property(ApplicationGatewayBackendHttpSettingsPropertiesFormat, Symbol("path"), path)
        setfield!(o, Symbol("path"), path)
        validate_property(ApplicationGatewayBackendHttpSettingsPropertiesFormat, Symbol("provisioningState"), provisioningState)
        setfield!(o, Symbol("provisioningState"), provisioningState)
        o
    end
end # type ApplicationGatewayBackendHttpSettingsPropertiesFormat

const _property_map_ApplicationGatewayBackendHttpSettingsPropertiesFormat = Dict{Symbol,Symbol}(Symbol("port")=>Symbol("port"), Symbol("protocol")=>Symbol("protocol"), Symbol("cookieBasedAffinity")=>Symbol("cookieBasedAffinity"), Symbol("requestTimeout")=>Symbol("requestTimeout"), Symbol("probe")=>Symbol("probe"), Symbol("authenticationCertificates")=>Symbol("authenticationCertificates"), Symbol("trustedRootCertificates")=>Symbol("trustedRootCertificates"), Symbol("connectionDraining")=>Symbol("connectionDraining"), Symbol("hostName")=>Symbol("hostName"), Symbol("pickHostNameFromBackendAddress")=>Symbol("pickHostNameFromBackendAddress"), Symbol("affinityCookieName")=>Symbol("affinityCookieName"), Symbol("probeEnabled")=>Symbol("probeEnabled"), Symbol("path")=>Symbol("path"), Symbol("provisioningState")=>Symbol("provisioningState"))
const _property_types_ApplicationGatewayBackendHttpSettingsPropertiesFormat = Dict{Symbol,String}(Symbol("port")=>"Int32", Symbol("protocol")=>"ApplicationGatewayProtocol", Symbol("cookieBasedAffinity")=>"String", Symbol("requestTimeout")=>"Int32", Symbol("probe")=>"SubResource", Symbol("authenticationCertificates")=>"Vector{SubResource}", Symbol("trustedRootCertificates")=>"Vector{SubResource}", Symbol("connectionDraining")=>"ApplicationGatewayConnectionDraining", Symbol("hostName")=>"String", Symbol("pickHostNameFromBackendAddress")=>"Bool", Symbol("affinityCookieName")=>"String", Symbol("probeEnabled")=>"Bool", Symbol("path")=>"String", Symbol("provisioningState")=>"ProvisioningState")
Base.propertynames(::Type{ ApplicationGatewayBackendHttpSettingsPropertiesFormat }) = collect(keys(_property_map_ApplicationGatewayBackendHttpSettingsPropertiesFormat))
Swagger.property_type(::Type{ ApplicationGatewayBackendHttpSettingsPropertiesFormat }, name::Symbol) = Union{Nothing,eval(Base.Meta.parse(_property_types_ApplicationGatewayBackendHttpSettingsPropertiesFormat[name]))}
Swagger.field_name(::Type{ ApplicationGatewayBackendHttpSettingsPropertiesFormat }, property_name::Symbol) =  _property_map_ApplicationGatewayBackendHttpSettingsPropertiesFormat[property_name]

const _allowed_ApplicationGatewayBackendHttpSettingsPropertiesFormat_cookieBasedAffinity = ["Enabled", "Disabled"]

function check_required(o::ApplicationGatewayBackendHttpSettingsPropertiesFormat)
    true
end

function validate_property(::Type{ ApplicationGatewayBackendHttpSettingsPropertiesFormat }, name::Symbol, val)
    if name === Symbol("cookieBasedAffinity")
        Swagger.validate_param(name, "ApplicationGatewayBackendHttpSettingsPropertiesFormat", :enum, val, _allowed_ApplicationGatewayBackendHttpSettingsPropertiesFormat_cookieBasedAffinity)
    end
end


text:
"""
    execute(f::Union{Function, Cmd})
    execute(f::Union{Function, Cmd}, env::Environment)
    execute(f::Union{Function, Cmd}, config::Config, args...)

Runs the function or command inside a playground.
"""
execute(f::Function, env::Environment) = withenv(f, env)
execute(f::Function) = execute(f, Environment())
execute(f::Function, config::Config, args...) = execute(f, Environment(config, args...))

function execute(cmd::Cmd, args...)
    execute(args...) do
        debug(logger, "Executing $cmd...")
        run(cmd)
    end
end


text:
# IO utilities for HMM.jl

import JSON
include("Base.jl")    # definitions of HMM

# load model from json
function HMM_from_json(json_path::String)
    json_dict = JSON.parsefile(json_path)
    HMM_type = json_dict["type"]
    J = length(json_dict["hidden_state_space"])

    if HMM_type == "discrete"
        A = reshape(json_dict["transition_matrix"], (J, :))
        B = reshape(json_dict["emission_matrix"], (J, :))
        model = HMM(
            json_dict["hidden_state_space"],
            json_dict["emission_space"],
            json_dict["initial_distribution"],
            A,
            B,
            json_dict["name"]
        )
    elseif HMM_type == "Gaussian"
        A = reshape(json_dict["transition_matrix"], (J, :))
        M = reshape(json_dict["Gaussian_center"], (J, :))
        σ² = reshape(json_dict["Gaussian_deviation"], (J, :))
        W = reshape(json_dict["mixture_coefficient"], (J, :))
        model = Gaussian_HMM(
            json_dict["hidden_state_space"],
            json_dict["initial_distribution"],
            A,
            M,
            σ²,
            W,
            json_dict["name"]
        )
    else
        error("HMM type not found. Type can be either " *
        "\"discrete\" or \"Gaussian\".")
    end
    return model
end

# save model to json
function HMM_to_json(model::HMM, json_path::String)
    HMM_dict = Dict(
        "type" => "discrete",
        "hidden_state_space" => model.hidden_state_space,
        "emission_space" => model.emission_space,
        "initial_distribution" => model.initial_distribution,
        "transition_matrix" => vec(model.transition_matrix),
        "emission_matrix" => vec(model.emission_matrix),
        "name" => model.name
    )
    open(json_path, "w") do f
        JSON.print(f, HMM_dict)
    end
end

function HMM_to_json(model::Gaussian_HMM, json_path::String)
    HMM_dict = Dict(
        "type" => "Gaussian",
        "hidden_state_space" => model.hidden_state_space,
        "initial_distribution" => model.initial_distribution,
        "transition_matrix" => vec(model.transition_matrix),
        "Gaussian_center" => vec(model.Gaussian_center),
        "Gaussian_deviation" => vec(model.Gaussian_deviation),
        "mixture_coefficient" => vec(model.mixture_coefficient),
        "name" => model.name
    )
    open(json_path, "w") do f
        JSON.print(f, HMM_dict)
    end
end


text:
# UCEvaluation: UC Evaluation by scenarios
function UCEvaluation(V, Pexp, Rup, Rdw, RESData)
    OpCost = 0.
    ALL_DUALS ? NC = 7 : NC =0 # Are we considering all duals or not?
    λ = zeros(N, NC*length(G)*length(T)+length(T))
    m = m_UCEval(V, Pexp, Rup, Rdw, RESData)
    for ω in 1:N
        for t in T
            set_normalized_rhs(m[:PowerBalance_Second][t], -(sum(Pd,dims=1)[t] - RES[t,ω]) )
            set_normalized_rhs(m[:Spillage_Limits][t], RES[t,ω])

            end

            # Evaluating 2nd stage
            optimize!(m)
            OpCost += JuMP.objective_value.(m)

            if ALL_DUALS == false # Use only elec balance dual
                λ[ω,:] =  -collect(shadow_price.(m[:PowerBalance_Second][t]) for t in T)
                else
                    λ[ω,:] =
                        [reshape(-collect(shadow_price.(m[:PowerBalance_Second][t]) for t in T),1,:)                                                    reshape(-collect(shadow_price.(m[:PMax2nd][g,t]) for g in G, t in T),1,:)                                                    reshape(-collect(shadow_price.(m[:PMin2nd][g,t]) for g in G, t in T),1,:)                                                    reshape(-collect(shadow_price.(m[:RplusLim][g,t]) for g in G, t in T),1,:)                                                    reshape(-collect(shadow_price.(m[:RminusLim][g,t]) for g in G, t in T),1,:)                                                    reshape(-collect(shadow_price.(m[:Ramps_2ndUp][g,t]) for g in G, t in T),1,:)                                                    reshape(-collect(shadow_price.(m[:Ramps_2ndDw][g,t]) for g in G, t in T),1,:)                                                    reshape(-collect(shadow_price.(m[:PGen_2nd][g,t]) for g in G, t in T),1,:)
                                ]

                end
        end
    # return λ, OpCost
    # end

    return λ, OpCost/N
    end

max_stars_repo_name: gonzalez-alvaro/MultidimensionalGAPM

max_issues_repo_name: gonzalez-alvaro/MultidimensionalGAPM

max_forks_repo_name: gonzalez-alvaro/MultidimensionalGAPM


text: struct S end

max_stars_repo_name: gabriel-doriath-dohler/pjulia

max_issues_repo_name: gabriel-doriath-dohler/pjulia

max_forks_repo_name: gabriel-doriath-dohler/pjulia


text:
"""
    TestPackage.jl

Hallo!
"""
module TestPackage

export plusTwo

"""
    plusTwo(x)

Sum the numeric "2" to whatever it receives as input

A more detailed explanation can go here, although I guess it is not needed in this case

# Arguments
* `x`: The amount to which we want to add 2

# Notes
* Notes can go here

# Examples
```julia
julia> five = plusTwo(3)
5
```
"""
plusTwo(x) = x + 2

end


text:
@testset "Cylinder" begin
    c = Cylinder([0,0,0], 0.3, 2.2, [0,0,1])
    @test c == deepcopy(c)
    @test hash(c) == hash(deepcopy(c))
    @test [0.2,0.2,1] ∈ c
    @test SVector(0.2,0.2,1.2) ∉ c
    @test [0.2,0.25,1] ∉ c

    @test ((x,nout) = surfpt_nearby([0,0,0],c); (x≈[0,0,1.1] && nout≈[0,0,1]) || (x≈[0.3,0,0] && nout≈[1,0,0]) || (x≈[0,0.3,0] && nout≈[0,1,0]))  # handle point at center properly
    @test all([(p = [0,0,1.1sz]; surfpt_nearby(p,c) ≈ (p,[0,0,sz])) for sz = (-1,1)])  # handle point on axis properly
    @test all([(p = [0.3sx/√2,0.3sy/√2,1.1sz]; surfpt_nearby(1.1p,c) ≈ (p, normalize(1.1p-p))) for sx = (-1,1), sy = (-1,1), sz = (-1,1)])  # outside corners
    @test all([(p = [0.3sx,0,1.1sz]; surfpt_nearby(1.1p,c) ≈ (p, normalize(1.1p-p))) for sx = (-1,1), sz = (-1,1)])  # outside corners
    @test all([(p = [0,0.3sy,1.1sz]; surfpt_nearby(1.1p,c) ≈ (p, normalize(1.1p-p))) for sy = (-1,1), sz = (-1,1)])  # outside corners
    @test all([(p = [0.3sx/√2,0.3sy/√2,1.1sz]; (x,nout) = surfpt_nearby(p,c); (x≈p && all([sx 0 0; 0 sy 0; 0 0 sz]*nout.≥-10eps()))) for sx = (-1,1), sy = (-1,1), sz = (-1,1)])  # on rims
    @test all([(p = [0.3sx,0,1.1sz]; (x,nout) = surfpt_nearby(p,c); (x≈p && all([sx 0 0; 0 0 sz]*nout.≥-10eps()))) for sx = (-1,1), sz = (-1,1)])  # on rims
    @test all([(p = [0,0.3sy,1.1sz]; (x,nout) = surfpt_nearby(p,c); (x≈p && all([0 sy 0; 0 0 sz]*nout.≥-10eps()))) for sy = (-1,1), sz = (-1,1)])  # on rims
    @test all([(p = [0.3sx/√2/2,0.3sy/√2/2,1.1sz]; surfpt_nearby([p[1],p[2],ρ*p[3]],c) ≈ (p,[0,0,sz])) for ρ = (one⁻⁻,1,one⁺⁺), sx = (-1,0,1), sy = (-1,0,1), sz = (-1,1)])  # around bases
    @test all([(p = [0.3sx,0,1.1sz/2]; surfpt_nearby([ρ*p[1],p[2],p[3]],c) ≈ (p,[sx,0,0])) for ρ = (one⁻⁻,1,one⁺⁺), sx = (-1,1), sz = (-1,0,1)])  # around side
    @test all([(p = [0,0.3sy,1.1sz/2]; surfpt_nearby([p[1],ρ*p[2],p[3]],c) ≈ (p,[0,sy,0])) for ρ = (one⁻⁻,1,one⁺⁺), sy = (-1,1), sz = (-1,0,1)])  # around side

    @test @inferred(normal([0.1,0.2,0.3], c)) ≈ [0.1,0.2,0] / hypot(0.1,0.2)
    @test normal([0.1,0.2,-1.11], c) == [0,0,-1]
    @test normal([0.31, 0, 0.3], c) == [1,0,0]
    @test bounds(c) ≈ ([-0.3,-0.3,-1.1],[0.3,0.3,1.1])
    @test checkbounds(c)
    @test checkbounds(Cylinder([1,17,44], 0.3, 1.1, [1,-2,3]))

    @test (∆ = rand(3); translate(c,∆) ≈ Cylinder([0,0,0]+∆, 0.3, 2.2, [0,0,1]))
end  # @testset "Cylinder"

@testset "Cylinder, rotated" begin
    ax1 = normalize([1,0,-1])
    ax2 = [0,1,0]
    ax3 = normalize([1,0,1])  # ax3 = ax1 × ax2
    cr = Cylinder([0,0,0], 0.3, 2.2, ax3)
    @test cr == deepcopy(cr)
    @test hash(cr) == hash(deepcopy(cr))

    @test ((x,nout) = surfpt_nearby([0,0,0],cr); (x≈0.3ax1 && nout≈ax1) || (x≈0.3ax2 && nout≈ax2) || (x≈1.1ax3 && nout≈ax3))  # handle point at center properly
    @test all([(p = 1.1s3*ax3; surfpt_nearby(p,cr) ≈ (p,s3*ax3)) for s3 = (-1,1)])  # handle point on axis properly
    @test all([(p = 0.3(s1*ax1+s2*ax2)/√2+1.1s3*ax3; surfpt_nearby(1.1p,cr) ≈ (p, normalize(1.1p-p))) for s1 = (-1,1), s2 = (-1,1), s3 = (-1,1)])  # outside corners
    @test all([(p = 0.3s1*ax1+1.1s3*ax3; surfpt_nearby(1.1p,cr) ≈ (p, normalize(1.1p-p))) for s1 = (-1,1), s3 = (-1,1)])  # outside corners
    @test all([(p = 0.3s2*ax2+1.1s3*ax3; surfpt_nearby(1.1p,cr) ≈ (p, normalize(1.1p-p))) for s2 = (-1,1), s3 = (-1,1)])  # outside corners
    @test all([(p = 0.3(s1*ax1+s2*ax2)/√2+1.1s3*ax3; (x,nout) = surfpt_nearby(p,cr); (x≈p && all([s1*ax1 s2*ax2 s3*ax3]'*nout.≥-10eps()) && norm(nout)≈1)) for s1 = (-1,1), s2 = (-1,1), s3 = (-1,1)])  # on rims
    @test all([(p = 0.3s1*ax1+1.1s3*ax3; (x,nout) = surfpt_nearby(p,cr); (x≈p && all([s1*ax1 s3*ax3]'*nout.≥-10eps()) && norm(nout)≈1)) for s1 = (-1,1), s3 = (-1,1)])  # on rims
    @test all([(p = 0.3s2*ax2+1.1s3*ax3; (x,nout) = surfpt_nearby(p,cr); (x≈p && all([s2*ax2 s3*ax3]'*nout.≥-10eps()) && norm(nout)≈1)) for s2 = (-1,1), s3 = (-1,1)])  # on rims
    @test all([surfpt_nearby(0.3(s1*ax1+s2*ax2)/√2/2+ρ*1.1s3*ax3,cr) ≈ (0.3(s1*ax1+s2*ax2)/√2/2+1.1s3*ax3,s3*ax3) for ρ = (one⁻⁻,1,one⁺⁺), s1 = (-1,0,1), s2 = (-1,0,1), s3 = (-1,1)])  # around bases
    @test all([surfpt_nearby(ρ*0.3s1*ax1+1.1s3*ax3/2,cr) ≈ (0.3s1*ax1+1.1s3*ax3/2, s1*ax1) for ρ = (one⁻⁻,1,one⁺⁺), s1 = (-1,1), s3 = (-1,0,1)])  # around side
    @test all([surfpt_nearby(ρ*0.3s2*ax2+1.1s3*ax3/2,cr) ≈ (0.3s2*ax2+1.1s3*ax3/2, s2*ax2) for ρ = (one⁻⁻,1,one⁺⁺), s2 = (-1,1), s3 = (-1,0,1)])  # around side

    @test all([@inferred(normal(0.3(s1*ax1+s2*ax2)/√2/2+ρ*1.1s3*ax3,cr)) ≈ s3*ax3 for ρ = (one⁻⁻,1,one⁺⁺), s1 = (-1,0,1), s2 = (-1,0,1), s3 = (-1,1)])  # around bases
    @test all([normal(ρ*0.3s2*ax2+1.1s3/2*ax3,cr) ≈ s2*ax2 for ρ = (one⁻⁻,1,one⁺⁺), s2 = (-1,1), s3 = (-1,0,1)])  # around side
    @test all([normal(ρ*0.3s1*ax1+1.1s3/2*ax3,cr) ≈ s1*ax1 for ρ = (one⁻⁻,1,one⁺⁺), s1 = (-1,1), s3 = (-1,0,1)])  # around side

    @test bounds(cr) ≈ (-[(1.1+0.3)/√2,0.3,(1.1+0.3)/√2], [(1.1+0.3)/√2,0.3,(1.1+0.3)/√2])
    @test checkbounds(cr)

    @test (∆ = rand(3); translate(cr,∆) ≈ Cylinder([0,0,0]+∆, 0.3, 2.2, ax3))
end  # @testset "Cylinder, rotated"


text:
export SimulatedGaussMeter, SimulatedGaussMeterParams, getXValue, getYValue, getZValue

Base.@kwdef struct SimulatedGaussMeterParams <: DeviceParams
  coordinateTransformation::Matrix{Float64} = Matrix{Float64}(I,(3,3))
end
SimulatedGaussMeterParams(dict::Dict) = params_from_dict(SimulatedGaussMeterParams, dict)

Base.@kwdef mutable struct SimulatedGaussMeter <: GaussMeter
  "Unique device ID for this device as defined in the configuration."
  deviceID::String
  "Parameter struct for this devices read from the configuration."
  params::SimulatedGaussMeterParams
  "Flag if the device is optional."
	optional::Bool = false
  "Flag if the device is present."
  present::Bool = false
  "Vector of dependencies for this device."
  dependencies::Dict{String, Union{Device, Missing}}
end

function init(gauss::SimulatedGaussMeter)
  @debug "Initializing simulated gaussmeter unit with ID `$(gauss.deviceID)`."

  gauss.present = true
end

neededDependencies(::SimulatedGaussMeter) = []
optionalDependencies(::SimulatedGaussMeter) = []

Base.close(gauss::SimulatedGaussMeter) = nothing

getXValue(gauss::SimulatedGaussMeter) = 1.0u"mT"
getYValue(gauss::SimulatedGaussMeter) = 2.0u"mT"
getZValue(gauss::SimulatedGaussMeter) = 3.0u"mT"
getTemperature(gauss::SimulatedGaussMeter) = 20.0u"°C"
getFrequency(gauss::SimulatedGaussMeter) = 0.0u"Hz"
calculateFieldError(gauss::SimulatedGaussMeter, magneticField::Vector{<:Unitful.BField}) = 1.0u"mT"


text:
__precompile__()

module EllipsisNotation

import Base: to_indices, tail
const ..   = Val{:..}()

@inline fillcolons(inds, I) = fillcolons((), inds, I)

@inline fillcolons(colons, ::Tuple{}, ::Tuple{}) = colons
@noinline fillcolons(colons, ::Tuple{}, ::Tuple) = throw(ArgumentError("too many indices provided"))
@inline fillcolons(colons, t::NTuple{N, <:Any}, ::NTuple{N, <:Any}) where {N} = colons
@inline fillcolons(colons, t::Tuple, s::Tuple) = fillcolons((colons..., :), tail(t), s)

@inline function to_indices(A, inds, I::Tuple{Val{:..}, Vararg{Any, N}}) where N
    # Align the remaining indices to the tail of the `inds`
    colons = fillcolons(inds, tail(I))
    to_indices(A, inds, (colons..., tail(I)...))
end

export ..

end # module


text:
"""
    get_carbon_intensity(start_date::ZonedDateTime, end_date::ZonedDateTime)

Returns a DataFrame of the nationwide forecast and actual carbon intensity between the
given `start_date` and `end_date`.
"""
function get_carbon_intensity(start_date::ZonedDateTime, end_date::ZonedDateTime)
    return _common_get_request(start_date, end_date, :intensity, "intensity")
end

"""
    get_generation_mix(start_date::ZonedDateTime, end_date::ZonedDateTime)

Returns a DataFrame of the national generation mix over period defined by the `start_date`
and `end_date`.
"""
function get_generation_mix(start_date, end_date)
    return _common_get_request(start_date, end_date, :generationmix, "generation")
end

"""
    get_todays_forecast(;regional::Bool=false, region::AbstractString="")

Returns a DataFrame of the forecast and actual carbon intensity between for the 48 hours
of the day the query is called. By default return national level data, pass `regional=true`
to return data with regional spatial resolution. To retreive data for a single region, pass
the `region = <desired region>` where <desired region> must be included in the the
`AVAILABLE_REGIONS`.
"""
function get_todays_forecast(;regional::Bool=false, region::AbstractString="")

    # TODO: When passing `region` != "", query the API for only that region,
    # as opposed to all regions. The reason this isn't done already is because the
    # retruned data has a different format when querying for a single region.

    if !isempty(region) && !in(region, keys(AVAILABLE_REGIONS))
        throw(DomainError("`$region` not in available regions, choose one of
        $(collect(keys(AVAILABLE_REGIONS)))"))
    end

    sd = Date(now())
    request_string = if regional
        "https://api.carbonintensity.org.uk/regional/intensity/$(sd)/fw48h"
    else
        "https://api.carbonintensity.org.uk/intensity/$(sd)/fw48h"
    end

    request = HTTP.request(
        "GET",
        request_string
    )
    data = JSON.parse(String(request.body))["data"]
    parsed_data = _parse_data(data)
    if !regional
        return _unpack_df(parsed_data, :intensity)
    elseif isempty(region)
        return _unpack_df(_unpack_df(parsed_data, :regions), :intensity)
    else
        df = _unpack_df(_unpack_df(parsed_data, :regions), :intensity)
        return @subset(df, :shortname.==region)
    end
end

"""
    get_carbon_intensity(start_date::ZonedDateTime, end_date::ZonedDateTime)

Returns a NamedTuple with two fields named `intensity` and `generation`.
Both tables contain data spanning the period defined by the `start_date` and `end_date`.
the `intensity` field contains a dataframe of the forecast data of the regional carbon
intensity, and the `generation` field contains a dataframe of the
regional generation as a percent of the total generation.
"""
function get_regional_data(start_date, end_date)
    # get 10 days period as API only allows requests of less than 14 days
    date_ranges = collect(Iterators.partition(start_date:Day(1):end_date, floor(Int, 10)))
    intensity_dfs = []
    generation_dfs = []

    for range in date_ranges
        try
            request = HTTP.request(
                "GET",
                "https://api.carbonintensity.org.uk/regional/intensity/$(first(range))/$(last(range))"
            )
            data = JSON.parse(String(request.body))["data"]
            parsed_data = _parse_data(data)
            region_unpack = _unpack_df(parsed_data, :regions)
            region_unpack.generationmix .= _convert_mix.(region_unpack.generationmix)
            intensity_df = _unpack_df(region_unpack, :intensity)
            generation_df = _unpack_df(region_unpack, :generationmix)

            push!(intensity_dfs, intensity_df)
            push!(generation_dfs, generation_df)
        catch e
            @warn("error $e for date range $range")
        end
    end
    return (;
        intensity = reduce(vcat, intensity_dfs),
        generation = reduce(vcat, generation_dfs)
    )
end

"""
function to fetch and process data return via the API in common format.
"""
function _common_get_request(start_date, end_date, unpack_column, url_branch)
    date_ranges = collect(Iterators.partition(start_date:Day(1):end_date, floor(Int, 10)))
    parsed_dfs = map(date_ranges) do range
        try
            request = HTTP.request(
                "GET",
                "https://api.carbonintensity.org.uk/$(url_branch)/$(first(range))/$(last(range))"
            )
            data = JSON.parse(String(request.body))["data"]
            parsed_data = _parse_data(data)
            return _unpack_df(parsed_data, unpack_column)
        catch e
            @warn("error $e for date range $range")
        end
    end

    return reduce(vcat, parsed_dfs)
end

"""
helper function to unpack the generation mix arrays into a single Dict.
"""
function _convert_mix(a)
    return Dict(getindex.(a, "fuel") .=> getindex.(a, "perc"))
end

"""
helper function to parse the JSON data requested.

# TODO: Don't convert each element of `data` to DataFrame as this adds a lot of
unnecessary allocations
"""
_parse_data(data::AbstractArray) = _parse_data(reduce(vcat, DataFrame.(data)))

function _parse_data(parsed_df::DataFrame)
    parsed_df.from = _parse_datetime.(parsed_df.from)
    parsed_df.to = _parse_datetime.(parsed_df.to)
    return parsed_df
end

"""
helper function to unpack nested dictionaries.
"""
function _unpack_df(df, unpack_col)
    # copy so we don't mutate the input
    parsed_df = deepcopy(df)
    # add new columns to unpack the intensity data
    _unpacked_values = _unpack_values(parsed_df[!, unpack_col])
    for (counter, key) in enumerate(keys(first(parsed_df[!, unpack_col])))
        parsed_df[!, key] = _unpacked_values[counter, :]
    end
    return select(parsed_df, Not(unpack_col))
end

_unpack_values(d::AbstractDict) = [v for v in values(d)]
_unpack_values(d::AbstractArray)= reduce(hcat, _unpack_values.(d))

"""
helper function to parse timestamps.
"""
function _parse_datetime(dt)
    return DateTime.(first.(split.(dt, "Z")), dateformat"y-m-dTH:M")
end


text:
using ZigZagBoomerang
using LinearAlgebra
using Random
using SparseArrays
using Test
using Profile
#using ProfileView
include("gridlaplace.jl")

# Define precision operator of a Gaussian random field
n = 100
Γ = 0.01I + gridlaplacian(Float64, n, n)

# Γ is very sparse
@show nnz(Γ)/length(Γ) # 0.000496

# ∇ϕ gives the negative partial derivative of log density
∇ϕ(x, i, Γ) = ZigZagBoomerang.idot(Γ, i, x) # partial derivative of ϕ(x) with respect to x[i]

function ∇ϕ!(y, x, Γ)
    for i in eachindex(x)
        y[i] = ZigZagBoomerang.idot(Γ, i, x) # partial derivative of ϕ(x) with respect to x[i]
    end
    y
end
# Random initial values
t0 = 0.0
x0 = randn(n*n)
θ0 = rand([-1.0,1.0], n*n)

# Rejection bounds
c = [norm(Γ[:, i], 2) for i in 1:n*n]

# Define ZigZag
Z = ZigZag(Γ, x0*0)
# or try the FactBoomerang
#Z = FactBoomerang(Γ, x0*0, 0.1)
#Z = Boomerang(Γ, x0*0, 0.1)

# Run sparse ZigZag for T time units and collect trajectory
T = 30.0
trace, _, (acc, num) = spdmp(∇ϕ, t0, x0, θ0, T, c, Z, Γ);
#trace, _, (acc, num) = pdmp(∇ϕ!, t0, x0, θ0, T, c, Z, Γ);
@time spdmp(∇ϕ, t0, x0, θ0, T, c, Z, Γ);
0