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