Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Problems with unit ramp limits #1029

Open
Mastomaki opened this issue May 30, 2024 · 4 comments
Open

Problems with unit ramp limits #1029

Mastomaki opened this issue May 30, 2024 · 4 comments

Comments

@Mastomaki
Copy link

Description

Ramping down will be difficult when time resolution changes.

How to reproduce

Set the following unit and unit-output node parameters for a simple generator unit:

fix_units_on: 1
unit_capacity: 100
minimum_operating_point: 0.4
ramp_up_limit: 0.1
ramp_down_limit: 0.1

Set the following model parameters:

temporal block resolution ["1h","1h","1h","1h","4h","4h"]
model start: 2021-05-01T00:00:00

We will then have the ramp down limit constraint with t_before and t_after as follows:

t_before=2021-05-01T03:00 (1_hour)->2021-05-01T04:00,
t_after=2021-05-01T04:00 (4_hours) ->2021-05-01T08:00

Realized constraint:

3.6 * unit_flow[t_after] >= 640 * units_on[t_after] -200 * units_on[t_before] + 0.9 * unit_flow[t_before] -800 * units_shut_down[t_after]

Let's suppose the the demand (and thus unit output) is as follows:

unit_flow[t_before] = 90
unit_flow[t_after] = 70

It should be possible within the 0.1 ramp limit but the constraint will not be fulfilled unless units_shut_down[t_after] > 0.

Expected behavior
The ramping should be possible without shutting down units.

Desktop (please complete the following information):

  • Which SpineOpt version? 0.7.2
  • Which Spineinterface version? 0.13.7
@nnhjy
Copy link
Collaborator

nnhjy commented May 30, 2024

Looks interesting to me as well. I got the ramp_down constraint as follows:
unit_flow[t_before(1h)] - 4 unit_flow[t_after(4h)] - 200 units_on[t_before(1h)] + 640 units_on[t_after(4h)] - 800 units_shut_down[t_after(4h)] <= 0

They look similar, but why are the coefficients for the two unit_flow 1 and 4 in mine instead of 0.9 and 3.6? I didn't use ramp_up_limit = 0.1, but it should be irrelevant to the ramp_down constraint.

The results seem to illustrate some magic under the hood. The unit_shut_down line overlaps with the unit_started_up.
image
For the units_shut_down [t_after]> 0, the model creates a counterpart units_started_up with the same value. An interpretation of that could be "starting up and shutting down simultaneously equals no change of online status."

However, it would be better if the variable units_shut_down is automatically activated when the parameter ramp_down_limit is used.

@Mastomaki
Copy link
Author

你 好!

I have no idea why my coefficient is 3.6 instead of 4.

But the main problem is that unit_flow[t_after] is multiplied by overlap_duration(t_after, t), in this case 4 hours, which makes it look like the unit is ramping up a lot.

Yes the units_started_up must be equal to units_shut_down because fix_units_on was set. This was also discussed in issue Simultaneous starts/shutdowns.

@Mastomaki
Copy link
Author

My coefficient is 3.6 instead of 4 because I defined a fix_ratio_out_in_unit_flow 0.9 between fuel and output.

@Mastomaki
Copy link
Author

Mastomaki commented May 31, 2024

Constraint code
This is my attempt to correct it:

function _build_constraint_ramp_down(m::Model, u, ng, d, s_path, t_before, t_after)
    @fetch units_on, units_shut_down, unit_flow = m.ext[:spineopt].variables
    t0 = _analysis_time(m)

    # auxiliary functions for calculating time durations
    overlap_duration_flow = t1 -> sum(overlap_duration(t1, t)
                for (u, n, d, s, t) in unit_flow_indices(m; unit=u, node=ng, 
                    direction=d, stochastic_scenario=s_path, t=t_overlaps_t(m; t=t1));
                init=0)
    
    overlap_duration_units_on = t1 -> sum(overlap_duration(t1, t)
                for (u, s, t) in units_on_indices(m; unit=u, stochastic_scenario=s_path, t=t1);
                init=0)
    
    overlap_duration_switched = t1 -> sum(overlap_duration(t1, t)
                for (u, s, t) in units_switched_indices(m; unit=u, stochastic_scenario=s_path, t=t1);
                init=0)    

    @build_constraint(
        + sum(
            + unit_flow[u, n, d, s, t] * overlap_duration(t_before, t)
            for (u, n, d, s, t) in unit_flow_indices(
                m; unit=u, node=ng, direction=d, stochastic_scenario=s_path, t=t_overlaps_t(m; t=t_before)
            )
            if !is_reserve_node(node=n);
            init=0,
        ) / overlap_duration_flow(t_before)

        - sum(
            + unit_flow[u, n, d, s, t] * overlap_duration(t_after, t)
            for (u, n, d, s, t) in unit_flow_indices(
                m; unit=u, node=ng, direction=d, stochastic_scenario=s_path, t=t_overlaps_t(m; t=t_after)
            )
            if !is_reserve_node(node=n);
            init=0,
        ) / overlap_duration_flow(t_after)

        + sum(
            + unit_flow[u, n, d, s, t] * overlap_duration(t_after, t)
            for (u, n, d, s, t) in unit_flow_indices(
                m; unit=u, node=ng, direction=d, stochastic_scenario=s_path, t=t_overlaps_t(m; t=t_after)
            )
            if is_reserve_node(node=n)
            && _switch(d; to_node=downward_reserve, from_node=upward_reserve)(node=n)
            && !is_non_spinning(node=n);
            init=0,
        ) / overlap_duration_flow(t_after)
        <=
         (
            + sum(  (
                    + _shut_down_limit(m, u, ng, d, s, t0, t_after)
                    - _minimum_operating_point(m, u, ng, d, s, t0, t_after)
                    - _ramp_down_limit(m, u, ng, d, s, t0, t_after)
                    )
                * _unit_flow_capacity(m, u, ng, d, s, t0, t_after)
                * units_shut_down[u, s, t]
                * duration(t)
                for (u, s, t) in units_switched_indices(m; unit=u, stochastic_scenario=s_path, t=t_after);
                init=0,
            ) / overlap_duration_switched(t_after)

            + sum(
                - _minimum_operating_point(m, u, ng, d, s, t0, t_after)
                * _unit_flow_capacity(m, u, ng, d, s, t0, t_after)
                * units_on[u, s, t]
                * duration(t)
                for (u, s, t) in units_on_indices(m; unit=u, stochastic_scenario=s_path, t=t_after);
                init=0,
            ) / overlap_duration_units_on(t_after)

            + sum(
                + _minimum_operating_point(m, u, ng, d, s, t0, t_after)
                * _unit_flow_capacity(m, u, ng, d, s, t0, t_after)
                * units_on[u, s, t]
                * duration(t)
                for (u, s, t) in units_on_indices(m; unit=u, stochastic_scenario=s_path, t=t_before);
                init=0,
            ) / overlap_duration_units_on(t_before)

            + sum(
                + _ramp_down_limit(m, u, ng, d, s, t0, t_after)
                * _unit_flow_capacity(m, u, ng, d, s, t0, t_after)
                * units_on[u, s, t]
                * duration(t)
                for (u, s, t) in units_on_indices(m; unit=u, stochastic_scenario=s_path, t=t_after);
                init=0,
            )
        )
        
    )
end

Results
As I result for

fix_units_on: 1
unit_capacity: 100
minimum_operating_point: 0.4
ramp_down_limit: 0.12
fix_ratio_out_in_unit_flow: 0.9

t_before=2021-05-01T03:00 (1_hour)->2021-05-01T04:00,
t_after=2021-05-01T04:00 (4_hours) ->2021-05-01T08:00

I get a very reasonable result:

0.9 * unit_flow[t_after] >= -8 * units_on[t_after] -40 * units_on[t_before] + 0.9 * unit_flow[t_before] -48 * units_shut_down[t_after]

Here the 0.9 comes just from fix_ratio_out_in_unit_flow because the output flow is automatically replaced by input flow in the constraint.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants