Skip to content

Three phase EV with a charge goal never proves: hosted result Feasible, uncapped CBC runs for hours #186

Closed

Description

@andig

A request evcc builds for a household that is close to the README example never proves on the joint path, so the hosted optimizer returns Feasible after its 10 s. Uncapped, CBC ran 110 minutes on it without closing the gap.

Request

221 steps of 15 min, 53 h. Two batteries: an EV with c_min 4140 W (6 A on three phases), c_max 11040 W, s_initial 10 kWh, s_goal 40 kWh at step 60 (08:00 next morning), and a 10 kWh home battery at 50 % with 5 kW each way. Dynamic tariff between 17 and 45 ct with ±1 ct noise, 119 distinct prices, 8 ct feed-in, 4 kWp PV bell, a piecewise constant 17 kWh/day household profile. Strategies charge_before_export and discharge_before_import.

What the solver does

Hosted, two attempts of the same request shape: Feasible in 8.2 s and 10.3 s. Locally under the production limits: probe 2.1 s unproven, cost stage 0.2 to 3.1 s, tie break LP Optimal, MILP at its cap, Feasible. Locally with an 80 s probe: still unproven. Uncapped: 110 minutes, killed.

Variants, each replayed with a 60 s limit and a 55 s probe:

variant path status
as sent split Feasible, probe unproven at 55 s
both strategies none split Feasible
every price, demand and PV value jittered to be unique split Feasible
home battery removed split Feasible
EV c_min 0 joint Optimal in 0.1 s
EV on one phase, c_min 1380 W, c_max 3680 W joint Optimal in 0.3 s

The minimum charge power of the three phase EV is the whole problem. The cost stage, which runs with the 1 ct absolute gap, proves the same model in 0.2 s. The probe has no gap and tries to close differences below a cent, which the semi-continuous charging variable over 60 cheap slots does not allow it to.

One more experiment

Giving the probe the cost stage's absolute gap, gapAbs=gap_abs * objective_scale on the probe solve, does not rescue this request at 2 s, but it turns the request from #170 into a joint Optimal in 0.7 s where it splits today and takes 5.5 s. Worth a look on its own.

The README (#181) uses the one phase variant so every chart is from an Optimal solve, locally and hosted. The three phase request is the common real world shape and deserves either a faster proof or an honest Optimal at the cost stage's gap.

Request, 221 steps
{"batteries":[{"c_max":11040,"c_min":4140,"charge_from_grid":true,"d_max":0,"p_a":0.000153252,"s_goal":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,40000,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"s_initial":10000,"s_max":50000,"s_min":0},{"c_max":5000,"c_min":0,"charge_from_grid":true,"d_max":5000,"p_a":0.000153252,"s_capacity":10000,"s_initial":5000,"s_max":10000,"s_min":0}],"eta_c":0.9,"eta_d":0.9,"grid":{},"strategy":{"charging_strategy":"charge_before_export","discharging_strategy":"discharge_before_import"},"time_series":{"dt":[771,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900,900],"ft":[468.2754,493.375,437.125,378,315.875,250.75,182.75,111.75,37.75,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37.75,111.75,182.75,250.75,315.875,378,437.125,493.375,546.625,596.875,644.25,688.625,730,768.5,804,836.5,866.125,892.75,916.375,937.125,954.875,969.625,981.5,990.375,996.25,999.25,999.25,996.25,990.375,981.5,969.625,954.875,937.125,916.375,892.75,866.125,836.5,804,768.5,730,688.625,644.25,596.875,546.625,493.375,437.125,378,315.875,250.75,182.75,111.75,37.75,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37.75,111.75,182.75,250.75,315.875,378,437.125,493.375,546.625,596.875,644.25,688.625,730,768.5,804,836.5,866.125,892.75,916.375,937.125,954.875,969.625,981.5,990.375,996.25,999.25,999.25,996.25,990.375,981.5,969.625,954.875,937.125,916.375,892.75,866.125,836.5,804,768.5,730,688.625,644.25,596.875,546.625,493.375,437.125,378,315.875,250.75,182.75,111.75,37.75,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"gt":[102.8,350,350,350,350,350,350,350,350,350,350,350,350,350,350,350,350,350,350,350,350,150,150,150,150,150,150,150,150,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,250,250,250,250,250,250,250,250,250,250,250,250,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,350,350,350,350,350,350,350,350,350,350,350,350,350,350,350,350,350,350,350,350,150,150,150,150,150,150,150,150,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,250,250,250,250,250,250,250,250,250,250,250,250,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,350,350,350,350,350,350,350,350,350,350,350,350,350,350,350,350,350,350,350,350,150,150,150,150,150,150,150,150],"p_E":[8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05,8e-05],"p_N":[0.000287,0.000295,0.000295,0.000299,0.00031,0.00032,0.000347,0.000388,0.000429,0.000433,0.000431,0.000387,0.000347,0.000302,0.000293,0.000284,0.000285,0.000279,0.000282,0.000282,0.000277,0.000276,0.000284,0.000279,0.000263,0.000268,0.00027,0.000268,0.000265,0.000267,0.000251,0.000249,0.000258,0.000253,0.000267,0.00025,0.000251,0.000258,0.000266,0.000255,0.000259,0.000262,0.000267,0.000252,0.000261,0.000249,0.000249,0.000258,0.000257,0.000257,0.000273,0.000269,0.000287,0.000279,0.000296,0.000306,0.000296,0.0003,0.00033,0.000334,0.00034,0.000358,0.000341,0.000336,0.000331,0.000315,0.000297,0.000303,0.0003,0.000278,0.000281,0.000271,0.000246,0.000238,0.000231,0.000216,0.000203,0.000172,0.000195,0.000205,0.000214,0.000214,0.000218,0.000233,0.000221,0.000228,0.000246,0.000248,0.00026,0.000267,0.000263,0.000263,0.00027,0.000286,0.000284,0.000279,0.000293,0.000291,0.000297,0.000306,0.000314,0.000327,0.00036,0.000384,0.000431,0.000447,0.000433,0.000394,0.000359,0.000291,0.000292,0.000302,0.000287,0.000297,0.000284,0.000286,0.000286,0.000268,0.000267,0.000281,0.000275,0.000267,0.000258,0.000265,0.000254,0.000259,0.00025,0.000267,0.000261,0.000254,0.000264,0.000251,0.000257,0.00026,0.000251,0.000258,0.000255,0.000265,0.000261,0.000253,0.000266,0.000267,0.000256,0.000253,0.000252,0.000264,0.00027,0.000273,0.000272,0.000294,0.000283,0.000304,0.000301,0.000313,0.000325,0.000333,0.000351,0.000349,0.000346,0.000329,0.000325,0.000317,0.000305,0.000308,0.000295,0.00029,0.000273,0.000261,0.000254,0.000234,0.000224,0.000214,0.000194,0.000183,0.000195,0.000198,0.000198,0.000209,0.000215,0.00023,0.000239,0.000237,0.000243,0.000252,0.000249,0.000261,0.000254,0.000278,0.000282,0.000285,0.00028,0.000281,0.000289,0.0003,0.000289,0.000303,0.000319,0.000335,0.000356,0.000384,0.000422,0.000442,0.000429,0.000392,0.000351,0.000298,0.00029,0.000292,0.00029,0.000286,0.000291,0.000281,0.000275,0.000275,0.000267,0.00027,0.000266,0.000265,0.000273,0.000264,0.000257]}}

🤖 Generated with Claude Code

Activity

  1. andig commented on Oct 4, 2026

    @andig
    MemberAuthor

    Root cause, measured on the attached request with the cost objective alone and a gapped probe, so the strategies play no part.

    The LP relaxation ignores c_min. The charging gate is the textbook pair c ≤ c_max·z and c ≥ c_min·z: for any c below c_min a fractional z satisfies both, so the relaxation charges the EV with whatever small amounts suit it, 28 slots between 28 and 365 Wh in the morning PV surplus and the leftover below one full slot. Its bound is 0.4685 € for both c_min values, since neither binds in the relaxation.

    EV c_min best integer schedule bound slack proof
    2070 W 0.4403 € 2.8 ct 1.4 s
    4140 W 0.3757 € 9.3 ct not in 110 min

    With c_min 4140 W every one of those small charges has to become a 4.1 kW slot or nothing, which costs 9 ct against the relaxation. The absolute gap is 1 ct, so CBC cannot stop on the bound and has to enumerate 884 binaries to prove the 9 ct are unavoidable. The 0.3757 € schedule it finds within 20 s is the optimum: with OPTIMIZER_GAP_ABS at 5 ct the same request proves to the same schedule in 1.3 s, at 10 ct too.

    So the defect is not the request and not CBC: the fixed 1 ct absolute gap sits below the relaxation slack the c_min formulation produces whenever a vehicle's minimum power is a sizeable share of a slot. On this request the slack is nine times the gap. Options:

    1. Scale the absolute gap with the request, e.g. a share of the cost the relaxation reports, with 1 ct as the floor.
    2. Tighten the formulation so the bound respects c_min: a cover cut on the number of active slots the goal energy needs, Σ z ≥ ⌈E / (c_max·dt)⌉, or the leftover below one slot forced onto a second active slot.
    3. Report Optimal when the cost stage stopped on its gap and the remaining bound slack is logged, which is what cost_stage_gap already carries.

    The probe getting the cost stage's gap at all is a separate, smaller point, see above.

  2. andig commented on Oct 4, 2026

    @andig
    MemberAuthor

    Tried all three options under the production limits, 10 s and one thread, on this request (221 steps), its 414 step sibling, the #170 request and the one phase variant the README uses.

    1. Absolute gap scaled with the request. gap = max(gap_abs, share × goal energy × mean import price), on the probe and the cost stage alike. Goal energy is what the vehicles still need for their s_goal.

    request share path status time cost against the true optimum
    this one 0.25 % (2.5 ct) joint Optimal 1.5 s −0.1 ct
    this one 0.5 % and up joint Optimal 1.4 s −1.5 ct
    414 steps 0.25 % joint Optimal 2.0 s −0.2 ct
    414 steps 2 % joint Optimal 1.7 s −2.3 ct
    #170 1 % joint Optimal 1.9 s identical
    one phase any joint Optimal 1.0 s identical

    Today both three phase requests split and come back Feasible after 7.9 s, #170 after 5.4 s.

    2. Cover cut Σ z ≥ ⌈E / (c_max·dt)⌉ on the slots before a goal. No effect on this request, still a 7.7 s split: the relaxation meets a count of 13 with 13 fractional slivers, each only has to carry c_min·z, so the bound does not move. It also makes the unreachable goal case infeasible (the goal is soft through s_goal_pen, the cut is hard) and produced an off-model result on #170. Dropped.

    3. Optimal when the cost stage stopped within a gap of its bound. Changes the label and nothing else: same 7.7 s, same schedule. The remaining bound slack on this request is 1.2 ct, on the 414 step one 4.8 ct.

    Recommendation: option 1 with a share of 0.25 % and the 1 ct floor. It is the only one that shortens the solve, 7.9 s to 1.5 s here, it decides the tie on the joint path so the strategies get their proven answer, and it gives up a tenth of a cent on this request where a fixed 1 ct gap cannot be closed at all. A request without a goal keeps the 1 ct floor and behaves as today. Option 3 can ride along for the splits that remain, it costs nothing, but it should not be the fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions