# # Getting started

# ## Installing the package

# UnitCommitment.jl was tested and developed with [Julia 1.10](https://julialang.org/). To install Julia, please follow the [installation guide on the official Julia website](https://julialang.org/downloads/). To install UnitCommitment.jl, run the Julia interpreter, type `]` to open the package manager, then type:

# ```text
# pkg> add UnitCommitment@0.4
# ```

# To solve the optimization models, a mixed-integer linear programming (MILP) solver is also required. Please see the [JuMP installation guide](https://jump.dev/JuMP.jl/stable/installation/) for more instructions on installing a solver. Typical open-source choices are [HiGHS](https://github.com/jump-dev/HiGHS.jl), [Cbc](https://github.com/JuliaOpt/Cbc.jl) and [GLPK](https://github.com/JuliaOpt/GLPK.jl). In the instructions below, HiGHS will be used, but any other MILP solver should also be compatible.

# ## Solving a benchmark instance

# We start this tutorial by illustrating how to use UnitCommitment.jl to solve one of the provided benchmark instances. The package contains a large number of deterministic benchmark instances collected from the literature and converted into a common data format, which can be used to evaluate the performance of different solution methods. See [Instances](../guides/instances.md) for more details. The first step is to import `UnitCommitment` and HiGHS.

using HiGHS
using UnitCommitment

# Next, we use the function `UnitCommitment.read_benchmark` to read the instance.

instance = UnitCommitment.read_benchmark("matpower/case14/2017-01-01");

# Now that we have the instance loaded in memory, we build the JuMP optimization model using `UnitCommitment.build_model`:

model =
    UnitCommitment.build_model(instance = instance, optimizer = HiGHS.Optimizer);

# Next, we run the optimization process, with `UnitCommitment.optimize!`:

UnitCommitment.optimize!(model)

# Finally, we extract the optimal solution from the model:

solution = UnitCommitment.solution(model)

# We can then explore the solution using Julia:

@show solution["Thermal production (MW)"]["g1"]

# Or export the entire solution to a JSON file:

UnitCommitment.write("solution.json", solution)

# ## Solving a custom deterministic instance

# In the previous example, we solved a benchmark instance provided by the package. To solve a custom instance, the first step is to create an input file describing the list of elements (generators, loads and transmission lines) in the network. See [Data Format](../guides/format.md) for a complete description of the data format UC.jl expects. To keep this tutorial self-contained, we will create the input JSON file using Julia; however, this step can also be done with a simple text editor. First, we define the contents of the file:

json_contents = """
{
    "Parameters": {
        "Version": "0.4",
        "Time horizon (h)": 4
    },
    "Buses": {
        "b1": {
            "Load (MW)": [100, 150, 200, 250]
        }
    },
    "Generators": {
        "g1": {
            "Bus": "b1",
            "Type": "Thermal",
            "Production cost curve (MW)": [0, 200],
            "Production cost curve (\$)": [0, 1000],
            "Initial status (h)": -24,
            "Initial power (MW)": 0
        },
        "g2": {
            "Bus": "b1",
            "Type": "Thermal",
            "Production cost curve (MW)": [0, 300],
            "Production cost curve (\$)": [0, 3000],
            "Initial status (h)": -24,
            "Initial power (MW)": 0
        }
    }
}
""";

# Next, we write it to `example.json`.

open("example.json", "w") do file
    return write(file, json_contents)
end;

# Now that we have the input file, we can proceed as before, but using `UnitCommitment.read` instead of `UnitCommitment.read_benchmark`:

instance = UnitCommitment.read("example.json");
model =
    UnitCommitment.build_model(instance = instance, optimizer = HiGHS.Optimizer);
UnitCommitment.optimize!(model)

# Finally, we extract and display the solution:

solution = UnitCommitment.solution(model)

#

@show solution["Thermal production (MW)"]["g1"]

#

@show solution["Thermal production (MW)"]["g2"]

# ## Solving a custom stochastic instance

# In addition to deterministic test cases, UnitCommitment.jl can also solve two-stage stochastic instances of the problem. In this section, we demonstrate the most simple form, which builds a single (extensive form) model containing information for all scenarios. See [Decomposition](../tutorials/decomposition.md) for more advanced methods.

# First, we need to create one JSON input file for each scenario. Parameters that are allowed to change across scenarios are marked as "uncertain" in the [JSON data format](../guides/format.md) page. It is also possible to specify the name and weight of each scenario, as shown below.

# We start by creating `example_s1.json`, the first scenario file:

json_contents_s1 = """
{
    "Parameters": {
        "Version": "0.4",
        "Time horizon (h)": 4,
        "Scenario name": "s1",
        "Scenario weight": 3.0
    },
    "Buses": {
        "b1": {
            "Load (MW)": [100, 150, 200, 250]
        }
    },
    "Generators": {
        "g1": {
            "Bus": "b1",
            "Type": "Thermal",
            "Production cost curve (MW)": [0, 200],
            "Production cost curve (\$)": [0, 1000],
            "Initial status (h)": -24,
            "Initial power (MW)": 0
        },
        "g2": {
            "Bus": "b1",
            "Type": "Thermal",
            "Production cost curve (MW)": [0, 300],
            "Production cost curve (\$)": [0, 3000],
            "Initial status (h)": -24,
            "Initial power (MW)": 0
        }
    }
}
"""
open("example_s1.json", "w") do file
    return write(file, json_contents_s1)
end;

# Next, we create `example_s2.json`, the second scenario file:

json_contents_s2 = """
{
    "Parameters": {
        "Version": "0.4",
        "Time horizon (h)": 4,
        "Scenario name": "s2",
        "Scenario weight": 1.0
    },
    "Buses": {
        "b1": {
            "Load (MW)": [200, 300, 400, 500]
        }
    },
    "Generators": {
        "g1": {
            "Bus": "b1",
            "Type": "Thermal",
            "Production cost curve (MW)": [0, 200],
            "Production cost curve (\$)": [0, 1000],
            "Initial status (h)": -24,
            "Initial power (MW)": 0
        },
        "g2": {
            "Bus": "b1",
            "Type": "Thermal",
            "Production cost curve (MW)": [0, 300],
            "Production cost curve (\$)": [0, 3000],
            "Initial status (h)": -24,
            "Initial power (MW)": 0
        }
    }
}
""";
open("example_s2.json", "w") do file
    return write(file, json_contents_s2)
end;

# Now that we have our two scenario files, we can read them using `UnitCommitment.read`. Note that, instead of a single file, we now provide a list.

instance = UnitCommitment.read(["example_s1.json", "example_s2.json"])

# If we have a large number of scenario files, the [Glob](https://github.com/vtjnash/Glob.jl) package can also be used to avoid having to list them individually:

using Glob
instance = UnitCommitment.read(glob("example_s*.json"))

# Finally, we build the model and optimize as before:

model =
    UnitCommitment.build_model(instance = instance, optimizer = HiGHS.Optimizer);
UnitCommitment.optimize!(model)

# The solution to stochastic instances follows a slightly different format, as shown below:

solution = UnitCommitment.solution(model)

# The solution for each scenario can be accessed through `solution[scenario_name]`. For conveniance, this includes both first- and second-stage optimal decisions:

solution["s1"]
