# Locational Marginal Prices

Locational Marginal Prices (LMPs) refer to the cost of supplying electricity at specific locations of the network. LMPs are crucial for the operation of electricity markets and have many other applications, such as indicating what areas of the network may require additional generation or transmission capacity. UnitCommitment.jl implements two methods for calculating LMPS: Conventional LMPs and Approximated Extended LMPs (AELMPs). In this tutorial, we introduce each method and illustrate their usage.

### Conventional LMPs

Conventional LMPs work by (1) solving the original SCUC problem, (2) fixing all binary variables to their optimal values, and (3) re-solving the resulting linear programming model. In this approach, the LMPs are defined as the values of the dual variables associated with the net injection constraints.

The first step to use this method is to load and optimize an instance, as explained in previous tutorials:

    using UnitCommitment
    using HiGHS

    instance = UnitCommitment.read_benchmark("matpower/case14/2017-01-01")
    model =
        UnitCommitment.build_model(instance = instance, optimizer = HiGHS.Optimizer)
    UnitCommitment.optimize!(model)

    [ Info: Building model...
    [ Info: Building scenario s1 with probability 1.0
    [ Info: Computing injection shift factors...
    [ Info: Computed ISF in 0.00 seconds
    [ Info: Computing line outage factors...
    [ Info: Computed LODF in 0.00 seconds
    [ Info: Applying PTDF and LODF cutoffs (0.00500, 0.00100)
    [ Info: Built model in 0.01 seconds
    [ Info: Setting MILP time limit to 86400.00 seconds
    [ Info: Solving MILP...
    Running HiGHS 1.12.0 (git hash: 755a8e027a): Copyright (c) 2025 HiGHS under MIT licence terms
    MIP has 4744 rows; 4104 cols; 15633 nonzeros; 1080 integer variables (1080 binary)
    Coefficient ranges:
      Matrix  [1e+00, 3e+02]
      Cost    [3e+01, 3e+04]
      Bound   [1e+00, 1e+02]
      RHS     [1e+00, 4e+02]
    Presolving model
    4382 rows, 2704 cols, 14776 nonzeros  0s
    3177 rows, 1985 cols, 11301 nonzeros  0s
    3148 rows, 1965 cols, 11570 nonzeros  0s
    Presolve reductions: rows 3148(-1596); columns 1965(-2139); nonzeros 11570(-4063)

    Solving MIP model with:
       3148 rows
       1965 cols (862 binary, 0 integer, 0 implied int., 1103 continuous, 0 domain fixed)
       11570 nonzeros

    Src: B => Branching; C => Central rounding; F => Feasibility pump; H => Heuristic;
         I => Shifting; J => Feasibility jump; L => Sub-MIP; P => Empty MIP; R => Randomized rounding;
         S => Solve LP; T => Evaluate node; U => Unbounded; X => User solution; Y => HiGHS solution;
         Z => ZI Round; l => Trivial lower; p => Trivial point; u => Trivial upper; z => Trivial zero

            Nodes      |    B&B Tree     |            Objective Bounds              |  Dynamic Constraints |       Work
    Src  Proc. InQueue |  Leaves   Expl. | BestBound       BestSol              Gap |   Cuts   InLp Confl. | LpIters     Time

             0       0         0   0.00%   1.86264515e-09  inf                  inf        0      0      0         0     0.1s
     R       0       0         0   0.00%   360642.328869   360642.544974      0.00%        0      0      0       958     0.1s
             1       0         1 100.00%   360642.328869   360642.544974      0.00%        0      0      0       958     0.1s

    Solving report
      Status            Optimal
      Primal bound      360642.544974
      Dual bound        360642.328869
      Gap               6e-05% (tolerance: 0.01%)
      P-D integral      5.20032049852e-10
      Solution status   feasible
                        360642.544974 (objective)
                        0 (bound viol.)
                        0 (int. viol.)
                        0 (row viol.)
      Timing            0.11
      Max sub-MIP depth 0
      Nodes             1
      Repair LPs        0
      LP iterations     958
                        0 (strong br.)
                        0 (separation)
                        0 (heuristics)
    [ Info: Verifying transmission limits...
    [ Info: Verified transmission limits in 0.00 seconds
    [ Info: No violations found

Next, we call `UnitCommitment.compute_lmp`, as shown below. The function accepts three arguments – a solved SCUC model, the LMP method, and a linear optimizer – and it returns a dictionary mapping `(scenario_name, bus_name, time)` to the marginal price.

    lmp = UnitCommitment.compute_lmp(
        model,
        UnitCommitment.ConventionalLMP(),
        optimizer = HiGHS.Optimizer,
    )

    OrderedCollections.OrderedDict{Tuple{String, String, Int64}, Float64} with 504 entries:
      ("s1", "b1", 1)  => 44.4253
      ("s1", "b2", 1)  => 44.4253
      ("s1", "b3", 1)  => 44.4253
      ("s1", "b4", 1)  => 44.4253
      ("s1", "b5", 1)  => 44.4253
      ("s1", "b6", 1)  => 44.4253
      ("s1", "b7", 1)  => 44.4253
      ("s1", "b8", 1)  => 44.4253
      ("s1", "b9", 1)  => 44.4253
      ("s1", "b10", 1) => 44.4253
      ("s1", "b11", 1) => 44.4253
      ("s1", "b12", 1) => 44.4253
      ("s1", "b13", 1) => 44.4253
      ("s1", "b14", 1) => 44.4253
      ("s1", "b1", 2)  => 44.4253
      ("s1", "b2", 2)  => 44.4253
      ("s1", "b3", 2)  => 44.4253
      ("s1", "b4", 2)  => 44.4253
      ("s1", "b5", 2)  => 44.4253
      ⋮                => ⋮

For example, the following code queries the LMP of bus `b1` in scenario `s1` at time 1:

    @show lmp["s1", "b1", 1]

    44.425275404366815

### Approximate Extended LMPs

Approximate Extended LMPs (AELMPs) are an alternative method to calculate locational marginal prices which attemps to minimize uplift payments. The method internally works by modifying the instance data in three ways: (1) it sets the minimum power output of each generator to zero, (2) it averages the start-up cost over the offer blocks for each generator, and (3) it relaxes all integrality constraints. To compute AELMPs, as shown in the example below, we call `compute_lmp` and provide `UnitCommitment.AELMP()` as the second argument.

This method has two configurable parameters: `allow_offline_participation` and `consider_startup_costs`. If `allow_offline_participation = true`, then offline generators are allowed to participate in the pricing. If instead `allow_offline_participation = false`, offline generators are not allowed and therefore are excluded from the system. A solved UC model is optional if offline participation is allowed, but is required if not allowed. The method forces offline participation to be allowed if the UC model supplied by the user is not solved. For the second field, If `consider_startup_costs = true`, then start-up costs are integrated and averaged over each unit production; otherwise the production costs stay the same. By default, both fields are set to `true`.

Warning

This method is still under active research, and has several limitations. The implementation provided in the package is based on MISO Phase I only. It only supports fast start resources. More specifically, the minimum up/down time of all generators must be 1, the initial power of all generators must be 0, and the initial status of all generators must be negative. The method does not support time-varying start-up costs, and only currently works for deterministic instances. If offline participation is not allowed, AELMPs treats an asset to be offline if it is never on throughout all time periods.

    instance = UnitCommitment.read_benchmark("test/aelmp_simple")

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

    UnitCommitment.optimize!(model)

    lmp = UnitCommitment.compute_lmp(
        model,
        UnitCommitment.AELMP(
            allow_offline_participation = false,
            consider_startup_costs = true,
        ),
        optimizer = HiGHS.Optimizer,
    )

    @show lmp["s1", "B1", 1]

    274.3333333333333

Theme

This document was generated with Documenter.jl version 1.15.0 on Wednesday 5 November 2025. Using Julia version 1.12.1.