A collection of thoughts, code and snippets.

Omarchy - Multi-monitor HDMI and Audio

Posted on Thursday, 16 October 2025

Configuring multi-monitor and choosing HDMI sound output in Omarchy

I added a vertical monitor to my Omarchy desk. I have my BenQ horizontal and have added a Samsung as a vertical screen on the side.

Video setup

Listing my monitors gives me the following:

hyprctl monitors

Monitor HDMI-A-1 (ID 0):
	3840x2160@60.00000 at 0x0
	description: Samsung Electric Company LS27A800U HNMW500048
	make: Samsung Electric Company
	model: LS27A800U
	physical size (mm): 600x340
	serial: -----------
	active workspace: 1 (1)
	special workspace: 0 ()
	reserved: 0 26 0 0
	scale: 1.50
	transform: 1
	focused: yes
	dpmsStatus: 1
	vrr: false
	solitary: 0
	solitaryBlockedBy: windowed mode,missing candidate
	activelyTearing: false
	tearingBlockedBy: next frame is not torn,user settings,missing candidate
	directScanoutTo: 0
	directScanoutBlockedBy: user settings,missing candidate
	disabled: false
	currentFormat: XRGB8888
	mirrorOf: none
	availableModes: 3840x2160@60.00Hz ...

Monitor HDMI-A-2 (ID 1):
	3840x2160@60.00100 at -2560x0
	description: BNQ BenQ EL2870U TBK01026SL0
	make: BNQ
	model: BenQ EL2870U
	physical size (mm): 620x340
	serial: -----------
	active workspace: 2 (2)
	special workspace: 0 ()
	reserved: 0 26 0 0
	scale: 1.50
	transform: 0
	focused: no
	dpmsStatus: 1
	vrr: false
	solitary: 0
	solitaryBlockedBy: not opaque
	activelyTearin#pactl list short sinks
#pactl list cardsg: false
	tearingBlockedBy: next frame is not torn,user settings,missing candidate
	directScanoutTo: 0
	directScanoutBlockedBy: user settings,missing candidate
	disabled: false
	currentFormat: XRGB8888
	mirrorOf: none
	availableModes: 3840x2160@60.00Hz ...

I edited the monitors file to have ~/.config/hypr/monitors.conf

monitor=HDMI-A-2,preferred,auto-left,1.5
monitor=HDMI-A-1,preferred,auto-right,1.5, transform, 1

so that the A-1 (Samsung) has the vertical transform. I might switch it to -1 and turn the monitor the other way, because I have less of a bezel on the top of the monitor, but this is the current setup. I'm still not sure which auto- command I need at minimum, but with these two commands - the screens align the way I want.

Audio setup

The next issue I have is that the audio output is connected from the back of the BenQ monitor to my speaker system, taking the audio along the HDMI channel. Sometimes, depending on which order the screens wake up, the audio output switches to the Samsung. For the moment, I haven't yet fully figured out how to lock the audio output to a single HDMI output, so I have two scripts to run - checking which outputs where. I'll appreciate the help if someone has suggestions.

I used these commands to list the cards and outputs:

pactl list short sinks
pactl list cards

And depending on which screen woke up first, it's either running this one:

pactl set-card-profile alsa_card.pci-0000_00_1f.3 output:hdmi-stereo-extra1
pactl set-default-sink alsa_output.pci-0000_00_1f.3.hdmi-stereo-extra1

or this one:

pactl set-card-profile alsa_card.pci-0000_00_1f.3 output:hdmi-stereo
pactl set-default-sink alsa_output.pci-0000_00_1f.3.hdmi-stereo

I'm hoping to figure out how to pin the audio to the BenQ monitor soon.


Omarchy - Setting screen brightness over HDMI by terminal

Posted on Wednesday, 10 September 2025

Omarchy

With the coming arrival of the end of Windows10, I installed Omarchy on one my Beelink MiniS12 N95, fully expecting just to play with it and revert back to Windows11 on the machine. Win11 was slow on the machine, but a decent cheap desktop to have connected to a screen. Omarchy on the Beelink, even on the tiny hardware, has been absolutely flying. To the point that it's now my main desktop for everything right now.

One thing I needed to do is to handle the brightness of the screen, from the command line, so I could toggle the screen brightness from the command line. I discovered I could use ddcutil which I installed using the package manager on Omarchy.

sudo ddcutil detect
Display 1
   I2C bus:  /dev/i2c-0
   DRM_connector:           card1-HDMI-A-2
   EDID synopsis:
      Mfg id:               BNQ - UNK
      Model:                BenQ EL2870U
      Product code:         31049  (0x7949)
      Serial number:        58M02252SL0
      Binary serial number: 21573 (0x00005445)
      Manufacture year:     2021,  Week: 34
   VCP version:         2.2

My screen is connected by HDMI, do I was able to get the information on it.

~ ❯ sudo usermod -aG i2c $USER

So I didn't want to sudo all the time for my screen display information, I added my user the control of the i2c bus. This could be a security weakening, there's other ways to do it, but for my case it's fine.

~ ❯ ddcutil detect
Display 1
   I2C bus:  /dev/i2c-0
   DRM_connector:           card1-HDMI-A-2
   EDID synopsis:
      Mfg id:               BNQ - UNK
      Model:                BenQ EL2870U
      Product code:         31049  (0x7949)
      Serial number:        58M02252SL0
      Binary serial number: 21573 (0x00005445)
      Manufacture year:     2021,  Week: 34
   VCP version:         2.2

Then getting and setting the brightness was done with the following commands: ddcutil getvcp 10 and ddcutil setvcp 10 20

~ ❯ ddcutil getvcp 10
VCP code 0x10 (Brightness                    ): current value =    40, max value =   100

~ ❯ ddcutil setvcp 10 20

To get a listing of what codes the screen supports, you can use ddcutil capabilities.

ddcutil capabilities
Model: EL2870U
MCCS version: 2.2
Commands:
   Op Code: 01 (VCP Request)
   Op Code: 02 (VCP Response)
   Op Code: 03 (VCP Set)
   Op Code: 07 (Timing Request)
   Op Code: 0C (Save Settings)
   Op Code: E3 (Capabilities Reply)
   Op Code: F3 (Capabilities Request)
VCP Features:
   Feature: 02 (New control value)
   Feature: 04 (Restore factory defaults)
   Feature: 05 (Restore factory brightness/contrast defaults)
   Feature: 08 (Restore color defaults)
   Feature: 0B (Color temperature increment)
   Feature: 0C (Color temperature request)
   Feature: 10 (Brightness)
   Feature: 12 (Contrast)
   Feature: 14 (Select color preset)
      Values:
         04: 5000 K
         05: 6500 K
         08: 9300 K
         0b: User 1
   Feature: 16 (Video gain: Red)
   Feature: 18 (Video gain: Green)
   Feature: 1A (Video gain: Blue)
   Feature: 52 (Active control)
   Feature: 60 (Input Source)
      Values:
         0f: DisplayPort-1
         11: HDMI-1
         12: HDMI-2
   Feature: 62 (Audio speaker volume)
   Feature: 72 (Gamma)
      Invalid gamma descriptor: 50 64 78 8c a0
   Feature: 7D (Unrecognized feature)
      Values: 00 01 02 (interpretation unavailable)
   Feature: 7E (Trapezoid)
      Values: 03 0F 10 11 12 (interpretation unavailable)
   Feature: 7F (Unrecognized feature)
   Feature: 80 (Keystone)
      Values: 01 02 03 (interpretation unavailable)
   Feature: 86 (Display Scaling)
      Values:
         01: No scaling
         02: Max image, no aspect ration distortion
         05: Max vertical image with aspect ratio distortion
         0c: Unrecognized value
         10: Unrecognized value
         11: Unrecognized value
         13: Unrecognized value
         14: Unrecognized value
         15: Unrecognized value
         16: Unrecognized value
         17: Unrecognized value
   Feature: 87 (Sharpness)
   Feature: 8D (Audio mute/Screen blank)
      Values: 01 02 (interpretation unavailable)
   Feature: AC (Horizontal frequency)
   Feature: AE (Vertical frequency)
   Feature: B2 (Flat panel sub-pixel layout)
   Feature: B6 (Display technology type)
   Feature: C0 (Display usage time)
   Feature: C6 (Application enable key)
   Feature: C8 (Display controller type)
   Feature: C9 (Display firmware level)
   Feature: CA (OSD/Button Control)
      Values:
         01: OSD disabled, button events enabled
         02: OSD enabled, button events enabled
   Feature: CC (OSD Language)
      Values:
         01: Chinese (traditional, Hantai)
         02: English
         03: French
         04: German
         05: Italian
         06: Japanese
         07: Korean
         09: Russian
         0a: Spanish
         0b: Swedish
         0d: Chinese (simplified / Kantai)
         0e: Portuguese (Brazil)
         0f: Arabic
         12: Czech
         14: Dutch
         1a: Hungarian
         1e: Polish
         1f: Romanian
   Feature: D6 (Power mode)
      Values:
         01: DPM: On,  DPMS: Off
         05: Write only value to turn off display
   Feature: DA (Scan mode)
      Values:
         00: Normal operation
         02: Overscan
   Feature: DC (Display Mode)
      Values:
         04: User defined
         05: Games
         0b: Unrecognized value
         0c: Unrecognized value
         0e: Unrecognized value
         0f: Unrecognized value
         12: Unrecognized value
         13: Unrecognized value
         21: Unrecognized value
   Feature: DF (VCP Version)

This was what my BenQ exposes.

Ease of use

Discovered that ddcutil allows relative up downs:

~ ❯ ddcutil setvcp 10 + 10
~ ❯ ddcutil setvcp 10 - 10

Next steps

I need to figure out how to wire up these commands to the brightness up / down key commands on Omarchy - so I can control the brightness on the keyboard. Still not sure how to get that configuration working, since it doesn't work out of the box with my screen with the default tooling.


Omarchy - Integrating screen brightness via key binds

Posted on Wednesday, 10 September 2025

Controlling External Monitor Brightness (DDC/CI) in Hyprland with a Real OSD

As a follow-up to the previous entry on how to get the brightness adapted by ddcutil, I actually asked OpenAI's Codex to wire it up in my keyboard bindings. Once it succeeded and get the osd wired up, I asked it to document the process. Here is the Codex generated documentation.

As a side note I created a small script to increase or decrease the brightness on the command line, that is executable.

#!/bin/bash
# brightness up/down script using ddcutil
STEP=10
case "$1" in
  up)   ddcutil setvcp 10 +$STEP ;;
  down) ddcutil setvcp 10 -$STEP ;;
  get)  ddcutil getvcp 10 ;;
  *)    echo "Usage: $0 {up|down|get}" ;;
esac

OpenAI Codex steps and explanations

  • Environment: Hyprland (Omarchy on Arch), SwayOSD, ddcutil
  • Goal: Make hardware brightness keys and Alt+F1/F2 control HDMI monitor brightness via DDC/CI, with a correct on-screen display (OSD).

The Problem

  • Omarchy’s default media bindings show the OSD and call brightnessctl, which targets laptop backlights—not external HDMI displays.
  • My script ~/bin/hdmi-brightness already adjusts HDMI brightness using ddcutil, but Hyprland wasn’t calling it from brightness keys.
  • Bonus ask: show an OSD reflecting the real HDMI brightness level.

Solution Summary

  • Unbind default brightness keys.
  • Bind brightness keys and Alt+F1/F2 to the hdmi-brightness script.
  • After each adjustment, read the real brightness via ddcutil getvcp 10 and display an OSD using SwayOSD’s custom-progress mode.

Keybindings

  • File: ~/.config/hypr/bindings.conf
  • Unbind defaults:
    • unbind = , XF86MonBrightnessUp
    • unbind = , XF86MonBrightnessDown
    • unbind = ALT, XF86MonBrightnessUp
    • unbind = ALT, XF86MonBrightnessDown
  • Bind to DDC/CI script + OSD:
    • bindeld = , XF86MonBrightnessUp, HDMI Brightness up, exec, bash -lc "~/bin/hdmi-brightness raise; read P R <<< $(ddcutil getvcp 10 2>/dev/null | awk 'BEGIN{FS=\"[=,]\"} /current value/ {cv=$2+0; mv=$4+0; if(mv<=0){p=0;r=0}else{p=int((cv*100+mv/2)/mv); r=cv/mv}; if(r<0) r=0; if(r>1) r=1; printf(\"%d %.2f\", p, r)}'); $osdclient --custom-icon display-brightness --custom-progress-text \"Brightness: $P%\" --custom-progress \"$R\""
    • bindeld = , XF86MonBrightnessDown, HDMI Brightness down, exec, bash -lc "~/bin/hdmi-brightness lower; read P R <<< $(ddcutil getvcp 10 2>/dev/null | awk 'BEGIN{FS=\"[=,]\"} /current value/ {cv=$2+0; mv=$4+0; if(mv<=0){p=0;r=0}else{p=int((cv*100+mv/2)/mv); r=cv/mv}; if(r<0) r=0; if(r>1) r=1; printf(\"%d %.2f\", p, r)}'); $osdclient --custom-icon display-brightness --custom-progress-text \"Brightness: $P%\" --custom-progress \"$R\""
    • bindeld = ALT, F2, HDMI Brightness up, exec, [same as above with raise]
    • bindeld = ALT, F1, HDMI Brightness down, exec, [same as above with lower]
  • $osdclient comes from Omarchy’s defaults and shows OSD on the focused monitor:
    • swayosd-client --monitor "$(hyprctl monitors -j | jq -r '.[] | select(.focused == true).name')"

OSD Integration

  • ddcutil getvcp 10 returns a line containing “current value” and “maximum value”.
  • We parse it to get:
    • P (percent, e.g., 72)
    • R (ratio 0.00–1.00 for a progress bar)
  • SwayOSD’s --custom-progress shows a clean bar without changing any backlight devices:
    • --custom-progress "$R"
    • --custom-progress-text "Brightness: $P%"
    • --custom-icon display-brightness

Testing

  • Ensure the script is executable: chmod +x ~/bin/hdmi-brightness
  • Reload Hyprland: hyprctl reload
  • Press brightness keys and Alt+F1/F2:
    • External monitor brightness changes (DDC/CI)
    • OSD shows an accurate bar and percentage
  • If no OSD appears:
    • systemctl --user enable --now swayosd
    • Keep a window on the monitor you want the OSD (defaults to focused)

Troubleshooting

  • No brightness change: confirm ddcutil works (ddcutil detect, ddcutil getvcp 10), user in i2c group, and i2c-dev is loaded.
  • Different keycodes: use wev to check actual keysyms and update the binds accordingly.
  • OSD on the wrong output: we can pin $osdclient to a specific monitor name (e.g., --monitor "HDMI-A-1").

Why This Works

  • It replaces backlight-centric controls with DDC/CI, which external monitors use.
  • The OSD is decoupled from any system backlight and directly reflects DDC/CI state, so it’s always accurate.

It’s a proportional allocation - how hard can it be? Going from Water filling to QP

Posted on Wednesday, 16 April 2025

It’s a proportional allocation - how hard can it be? MIQP

Alice, Bob and Charlie buy a pizza and they each put down a part of the price, respectively 50%, 30% and 20%. Pizza arrives and they slice it up and eat it. But Alice gets full after eating 40% of her 50% slice, how do we allocate the remaining 10% slice to Bob and Charlie? Her 10% can be cut up into 2% slices and we proportionally give 3 to Bob (total 36%) and 2 to Charlie (total 24%).

We have a total allocation of 100 MW of power to allocate to three products, in the ideal allocation with (0.5, 0.3, 0.2) ratio and each product has a maximum of 40MW.

These two problems are the same.

Waterfilling algorithm

There exists a well known algorithm for solving this problem, the water filling algorithm.

In essence, we are looking at finding the level of water across three containers that is flat along the allocation amount.

definitions:

  • \(w_i\) is the weight from product i
  • \(w'_i\) is the updated weight when we have fewer products due to saturation
  • \(M_i\) is the maximum of product i
  • \(x'_i\) is the initial / ideal allocation in case the products are not saturated
  • \(x_i\) is the current allocation to the product
  • \(T\) is total allocation amount to spread over the products
  • \(T_r\) is the remaining allocation to spread over products after the saturated products are removed from T
  • \(saturated\) : means that the allocation is >= max on the product.
  • \(unsaturated\) : means that the allocation is < max on the product

Water-Filling (Iterative) Algorithm:

  • Step 1: Compute the ideal allocations \(x'_i = w_i \cdot T\)
  • Step 2: For any product i for which \(x'_i >= M_i\) (saturated), set \(x_i =M_i\), otherwise \(x_i = x'_i\).
  • Step 3: Compute the remaining capacity by removing the capacity of saturated \(x_i\) : \(T_r = T − \sum_{i_{\text{saturated}}} M_i\).
  • Step 4: For the remaining (unsaturated) products, redistribute \(T_r\) proportionally based on their weights normalized over the unsaturated set \(x_i = w^{\prime}_i \cdot T_r\) where \(w^{\prime}_i = \frac{w_i}{\sum_j w_j}\) with \(j\) representing the unsaturated products
  • Step 5: Repeat the process if additional products get saturated during the redistribution, ie. from Step 2.

The reason this terminates is that because we remove saturated products from the list, the next products get allocated and the set gets reduced, either a new product is saturated and the cycle continues or the final allocation is done and terminates.

import numpy as np

def iterative_waterfilling(T, weights, max_allocations):
    n = len(weights)
    allocations = np.zeros(n)
    unsaturated = np.array([True] * n)
    remaining_T = T

    while True:
        # Calculate proportional weights for unsaturated products
        current_weights = np.array(weights) * unsaturated
        total_current_weight = np.sum(current_weights)
        
        # Calculate ideal allocation for unsaturated products
        ideal_allocations = (current_weights / total_current_weight) * remaining_T

        # Check for saturation
        newly_saturated = ideal_allocations >= max_allocations
        
        # Update allocations and saturation status
        if not np.any(newly_saturated & unsaturated):
            allocations[unsaturated] = ideal_allocations[unsaturated]
            break
        
        for i in range(n):
            if unsaturated[i] and newly_saturated[i]:
                allocations[i] = max_allocations[i]
                unsaturated[i] = False
                remaining_T -= allocations[i]

    return allocations

T = 100
weights = [0.5, 0.3, 0.2]
max_allocations = [40, 40, 40]

allocations_result = iterative_waterfilling(T, weights, max_allocations)
print("Iterative Waterfilling Result:", allocations_result)

Examples

Example 1 - total 100, allocation (0.5,0.3,0.2), maximum (40,40,40), result (40,36,24)

Example 2 - total 100, allocation (0.5,0.3,0.2), maximum (40,34,40), result (40,34,26)

The problem of this algorithm is that while it works wonderfully for simple proportional problems, as soon as you start adding more constraints (minimums) and relations between the allocations, this iterative algorithm doesn’t work that great.

So how do we solve this as an optimization problem? We want to find a solution that when unconstrained (unsaturated) falls back to the proportional allocation and when constrained (saturated maximum) falls back to the waterfilling algorithm.

From Waterfilling to QP : Mixed integer quadratic programming

While the iterative waterfilling algorithm effectively solves basic proportional allocation problems, it struggles under more complex scenarios involving additional constraints, such as minimum allocation limits or relational constraints between allocations. To robustly handle these real-world complexities, we leverage Mixed Integer Quadratic Programming (MIQP). MIQP elegantly generalizes the waterfilling logic into an optimization framework, allowing precise specification of constraints and objectives. By translating allocation decisions into a mathematical optimization problem, we ensure optimal, constraint-respecting allocations, making it suitable for applications demanding reliability and flexibility.

Definitions:

  • \(T\): total
  • \(w_i\): weight
  • \(M_i\): Maximum of allocation
  • \(d_i\): product saturated marker ($\in \mathbb, binary \in \lbrace 0,1 \rbrace $), 0 unsaturated, 1 saturated
  • \(x_i\): allocation amount
  • \(v_i\): target water level for unsaturated product (shared identity)
  • \(U\): upper large bound

Objective:

\(\min \sum_i (x_i - w_i T)^2\)

Constraints:

(I) Basic allocation limits

\(\forall i \quad x_i \geq 0 \quad (a)\)

\(\forall i \quad x_i \leq M_i \quad (b)\)

(II) Total allocation constraint

\(\sum_i x_i \leq T\)

(III) Saturation constraints \(\forall i\)

\(x_i = w_i \cdot v_i + M_i \cdot d_i \quad (a)\)

\(v_i \leq U \cdot (1 - d_i) \quad (b) \quad \text{with } v_i = 0 \text{ when saturated}\)

\(v_i \geq 0 \quad (c)\)

\(v_i \leq \frac{M_i}{w_i} \cdot (1 - d_i) + U \cdot d_i \quad (d)\)

(IV) Water level equality constraints across unsaturated products

\(\forall i (1 \rightarrow n), \quad \forall j (i+1 \rightarrow n)\)

\(v_i - v_j \leq U \cdot (d_i + d_j)\)

\(v_j - v_i \leq U \cdot (d_i + d_j)\)

If both are unsaturated, it means \(d_i = d_j = 0\), thus forcing \(v_i = v_j\).

Explanation of \(v_i\)

This set of equations sets up \(v_i\) as a shared value \(z\) across all unsaturated products.

$$40 + (w_1 + w_2) \cdot z = 100$$

\((0.3 + 0.2) z = 60\)

\(z = 120\)

\(x_1 = 0.3 \cdot 120 = 36\)

\(x_2 = 0.2 \cdot 120 = 24\)

Implementation in Python

#pip install numpy
#pip install cvxpy
#pip install ecos

import cvxpy as cp
import numpy as np

# Problem parameters
T = 100.0
w = [0.5, 0.3, 0.2]          # target weights for products 1, 2, and 3
M_max = [40.0, 40.0, 40.0]     # maximum allocation for each product

# Number of products
n = len(w)

# A sufficiently large constant U (big-M) for enforcing water-level equality.
U = 500.0

# Decision variables:
# x: allocation amounts
x = cp.Variable(n)
# v: auxiliary "water-level" variables for unsaturated products
v = cp.Variable(n)
# delta: binary variables; delta[i] = 1 means product i is saturated (x[i] = M_max[i])
delta = cp.Variable(n, boolean=True)

constraints = []

# For each product, define the allocation as the sum of the unsaturated part and the saturation term.
for i in range(n):
    # If not saturated (delta[i] = 0) then x[i] = w[i]*v[i].
    # If saturated (delta[i] = 1) then x[i] = M_max[i].
    constraints.append(x[i] == w[i] * v[i] + M_max[i] * delta[i])
    
    # Force v[i] = 0 when saturated, by bounding v[i] to 0 when delta[i]=1.
    constraints.append(v[i] <= U * (1 - delta[i]))
    # Ensure nonnegativity of v.
    constraints.append(v[i] >= 0)
    # When unsaturated (delta[i]=0), we must have x[i] = w[i]*v[i] ≤ M_max[i]. 
    # Throught: Why not T here instead of M_max[i]? -> it would be correct, but M_max[i] is a more restrictive boundary so helps convergence
    constraints.append(v[i] <= (M_max[i] / w[i]) * (1 - delta[i]) + U * delta[i])

# Enforce that all unsaturated products share the same water-level.
# For every pair (i,j), if both are unsaturated (delta[i] = delta[j] = 0) then v[i] must equal v[j].
for i in range(n):
    for j in range(i+1, n):
        constraints.append(v[i] - v[j] <= U * (delta[i] + delta[j]))
        constraints.append(v[j] - v[i] <= U * (delta[i] + delta[j]))

# Total allocation constraint: the sum of all allocations must equal the available capacity.
constraints.append(cp.sum(x) == T)

# Ensure each x[i] does not exceed its maximum. (These could be built in via the definition of x.)
for i in range(n):
    constraints.append(x[i] >= 0)
    constraints.append(x[i] <= M_max[i])

# Define the target allocation for each product (unconstrained ideals)
target = np.array([w_i * T for w_i in w])

# Objective: minimize squared deviation from the ideal allocation.
objective = cp.Minimize(cp.sum_squares(x - target))

# Define and solve the MIQP.
# Use a MIQP-capable solver such as GUROBI, CPLEX, ECOS_BB, etc.
prob = cp.Problem(objective, constraints)
result = prob.solve(solver=cp.ECOS_BB)


print("Status:", prob.status)
print("Optimal value:", result)
print("Optimal allocations:")
for i in range(n):
    print(f"  x[{i+1}] = {x.value[i]:.4f}")
print("Water-level (v) values:")
for i in range(n):
    print(f"  v[{i+1}] = {v.value[i]:.4f}")
print("Saturation indicators (delta) values:")
for i in range(n):
    print(f"  delta[{i+1}] = {delta.value[i]:.4f}")

# Expected behavior for this example:
# - For product 1, the unconstrained target is 50 but M_max[0]=40, so we expect it to be saturated (delta[0]=1, x[0]=40).
# - For products 2 and 3 (unsaturated, delta = 0), they share the same water-level z.
#   The total allocation constraint becomes: 40 + (w[1] + w[2]) * z = 100  -->  (0.3+0.2)*z = 60, so z = 120.
#   Hence, x[2] = 0.3 * 120 = 36 and x[3] = 0.2 * 120 = 24.

Summary

In business applications, we often see allocations that should be "preference based" if unconstrained, but then with different constraints it becomes complicated to tease out the preferences in an optimal way. This QP application shows a way to have an allocation identical to the water filling, but with the flexibility of QP.

If you were simplifying the QP constraints to remove the equations (IV), the output would be (40, 35, 25) since that minimises the objective function.

This example here is taken out of an abstration of a practical problem of allocating ancillary service capacity to a series of contracts, with trader preferences. This chapter is part of my book "Energy - From Asset to Cashflow" (not yet published).