Update: C-rate clipping not accounted for in annual_excess_gen?

Continuing the discussion from Clarification on annual_excess_gen sign convention in hybrids battery simulation:

This is a follow-up to my previous thread on the excess-generation calculation in hour_simulation(). After correcting the sign issue discussed there, I found a second, independent issue related to the battery charging C-rate limitation, which explains the remaining energy-balance discrepancy in my model.

During charging, soc_usage is calculated as:

soc_usage = max(net_load * n_chg * battery_inv_eff / battery_size, -c_rate_chg)

When the available excess generation would require a charging rate above the battery C-rate, soc_usage is therefore clipped to -c_rate_chg.

The problem is that the difference between the available excess generation and the C-rate-limited charging amount is then not included in annual_excess_gen:

if (soc_prev - soc_usage > 1) or (soc > 1):
    # If State of Charge...
    annual_excess_gen += (soc_prev - soc_usage - 1) / n_chg / battery_inv_eff \*
                         battery_size
    soc = 1

(The linked previous thread describes why I changed the sign - soc_usage in the calculation of above’s annual_excess_gen - I am still looking for confirmation of this).

For example:

soc_prev        = 1.0
net_load        = -8 kW
n_chg           = 0.92
battery_inv_eff = 0.93
battery_size    = 5.2 kWh
c_rate_chg      = 1

The unconstrained SOC change would be:

-8 × 0.92 × 0.93 / 5.2 = -1.3163

but the C-rate limits this to:

soc_usage = -1

This corresponds to only:

1 × 5.2 / (0.92 × 0.93) = 6.0776 kWh

of charging, although 8 kWh of excess generation is available.

Therefore:

8.0000 - 6.0776 = 1.9224 kWh

of excess generation is already clipped by the C-rate and needs to be accounted for separately.

This appears to explain the remaining energy-balance discrepancy in my model. With the original calculation, I obtain:

Annual excess generation = 14856.735 kWh
Energy balance           = 1214.710 kWh

After accounting for the C-rate-clipped excess, I obtain:

Annual excess generation = 16071.034 kWh
Energy balance           = 0.411 kWh

The difference in annual excess generation is 1214.299 kWh, which corresponds almost exactly to the previously unexplained energy in the annual balance.

I therefore think the excess-generation calculation needs to account for both:

  1. energy that would cause the SOC to exceed 1, and
  2. energy that is already clipped because the battery charging C-rate has been reached.

I found that this can be corrected with a relatively small modification to the existing calculation by explicitly adding the C-rate-clipped amount:

if (soc_prev - soc_usage > 1) or (soc > 1):
    # If State of Charge...

    if (-c_rate_chg > net_load \* n_chg \* battery_inv_eff / battery_size):
       c_rate_clipped_charge = -c_rate_chg - (net_load \* n_chg \* battery_inv_eff
                               / battery_size)
    else:
       c_rate_clipped_charge = 0
    
    annual_excess_gen += (soc_prev - soc_usage - 1 + c_rate_clipped_charge) /n_chg
                         / battery_inv_eff \* battery_size
    soc = 1

A potentially easier solution altogether would be to define annual_excess_gen based on a newly defined actual battery charging energy and the net_load:

# Store how much battery energy was charged (if used).
    if net_load < 0:
        battery_charge = 0

        if battery_size > 0:
            battery_charge = min(-net_load, battery_chargeable)
            annual_battery_charge += battery_charge
            
            # If State of Charge...
            annual_excess_gen += (-net_load - battery_charge)
            if soc > 1:
                soc = 1

        else:
            annual_excess_gen += -net_load

battery_chargeable thereby simply follows its previous definition in hour_simulation_pv_biomass and represents the maximum energy that can actually be accepted by the battery in that hour.

I would be interested to hear whether this interpretation is consistent with the intended battery dispatch logic in OnSSET and would highly appreciate if someone could verify these findings.

I am also very happy to contribute in any way that helps move this fantastic tool forward. :slight_smile:

Best,
Stuart

Hi Stuart,

Thanks so much for flagging this! It’s a really interesting find and clearly took some careful digging on your end.

I pulled up the current hour_simulation() in hybrids.py to check this against the master, and I can see both of the issues you’re talking about.

The sign inconsistency you flagged is still present: line 239 uses soc_prev + soc_usage - 1, while the condition it sits inside on line 236 uses soc_prev - soc_usage. Your correction is the mathematically consistent one.

battery_chargeable (line 143) already correctly combines the C-rate and headroom limits, but it’s currently only used for the diesel-dispatch decision, never to cap soc_usage, so your second finding checks out too.

On the possible fixes, I’d lean towards option 2, since battery_chargeable is already defined exactly where you’d need it. One thing worth adding before this is ready to merge: could you post how soc itself gets updated under the rewrite? The excess-gen and battery-charge terms look right, but the SOC update line isn’t shown, and getting that wrong could reopen the energy-balance gap even with the rest fixed.

It’d be great if you could open this as a pull request covering both fixes together (sign and C-rate), since fixing only one won’t fully close the energy balance. Also worth noting: there seem’s to be an unrelated no-op at line 140 (self-discharge isn’t actually applied to soc, since the result of soc - 0.0002 * soc is never assigned). Small thing, but maybe worth its own quick PR.

Thanks again for putting in the work on this one, it really helps for the maintanence of the tool and is great when the community get involved in the development. The tool’s GitHub maintenance team can take a look and share their thoughts and see if they agree, either here or directly on GitHub.

Best, Nicky

1 Like

Thanks a lot for doublechecking Nicky! I am genuinely glad that these issues seem legit.

As for the non-applied self-discharge:

This one I have already flagged in May ( Clarification on battery self-discharge implementation in hour_simulation (SOC not updated?) ) and got a response and confirmation from @AndreasSahlberg. I have recently submitted the according Pull Request: “Fix battery self-discharge implementation - #165” after my first Pull Request had failed in May (must have been a mistake on my side - was still pretty new with GitHub at that stage).

So, yes. I am very happy to make a clean suggestion on this:

and will be looking forward to your feedback.

All the best,

Stuart

Hi stuart,
Sounds great I look forward to seeing the update - hopefully after August @AndreasSahlberg may be able to check if there is already an existing version that deals with this issue or if it has already been addressed elsewhere. Thanks again and do let us know if you notice anything else! And I’d be interested to hear about any projects you are working on using OnSSET! best Nicky