-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
clipping off-mpp operating point calculation #1178
Comments
I agree, would be a nice feature. Somehow it should accept any of the inverter models, or, be some kind of extension within the existing functions so that a user doesn't have to choose to call an inverter function or this new function. We could also open issues for:
|
may be related to #1199 ? |
I'd be happy to work on this in the medium term future. Is there a reference that I should work off of? Is a reference necessary for what would essentially be a point on a curve finder? |
@kurt-rhee I think the request is to find the point on the IV curve where the AC output of the inverter matches a desired value. Also, if the inverter has multiple |
Hi Kurt, I’m happy to jump on a call to discuss. I’m not sure if there are any references, but if not we could put this in the example gallery. I think the description already explains it well, but to clarify, the system must operate off-MPP when clipping, but finding this operating point is not trivial. It’s subject to two constraints:
So we probably need to iterate or use a bounded root finder like |
Another consideration: maximum/minimum voltage/current limitations of the inverter. If there is no suitable point to the right of the MPP, should the algorithm "fall back" to looking for a suitable point on the left (low voltage, high current) side? Whether the algorithm interpolates a pre-computed I-V curve, or is given SDM parameters and calls The list of considerations here is already long enough to make me wonder if we are "inventing" too much. Has anyone looked for a reference we could follow? I haven't. |
I also haven't been able to find a reference. Both PlantPredict and SolarFarmer have some notes about keeping the inverter input voltage and current within some operating window, but neither have references. https://terabase.atlassian.net/servicedesk/customer/portal/3/article/1292009483 |
Good points about minimum voltage and other constraints. PVsyst also has help page with nice diagram. I propose one of two options:
IMHO the reference situation is getting out of hand. Solving a set of equations is math. Only implementations of algorithms should require a reference. We are not describing a sky model here, the two situations are not comparable. There are generally available references for math and other basic solar facts. |
Good references serve different useful purposes, but perhaps there's no need to get hung up on that right now if the fall-back is example gallery? Once there is a solution, we'll see how straight-forward it is. |
This is interesting: Essentially we are putting a (temperature-dependent) upper bound on the quantity that we are trying to maximize. I don’t think anything a-priori prevents one from defining this like any other nonlinear constraint https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.NonlinearConstraint.html#scipy.optimize.NonlinearConstraint, and then solving for the maximum power as before, but under this additional constraint. I might try to add this feature to the MPPT-under-mismatch algorithm that I put together in #1923. Of course, the devil might be in the details! |
Okay finally getting around to this. My initial thought is to create a function to solve for maximum power at the given ambient temperature #1199 and then I like @mikofski 's brentq solution for calculating the operating point given the constraint. In a utility scale photovoltaic site with a central inverter, a given inverter will be connected to a set of combiners. Each of these combiners will then be connected to a set of modules which are not necessarily unique. For example, even intra-combiner there can be strings that have different bin classes. This means that we will need to combine all of the IV curves together into a single IV curve before solving for the point that satisfies the maximum power constraint. This would then necessitate that a user provide a given IV curve in a 2D array of voltages and their corresponding current values. Perhaps I am overthinking this though, looking forward to hearing everyone's thoughts. |
Regarding…
While this is true, if we only consider the “ideal” case, the situation when not all irradiance, module, & string conditions are known, could we first test the general case where the entire system is uniform? Then afterward we could provide particular solutions for more detailed scenarios? Would you be open to this approach first? |
That's how a single MPPT works, but I don't think that's how clipping works for the whole inverter. I'm no expert by any means, but my understanding is that each MPPT applies the DC current and power limits. In contrast, the temperature limit is applied to the entire device, and I don't know how a temperature curtailment is apportioned among the MPPTs. |
Maybe for simplicity sake we can descope away from temperate derating for now and just find the voltage that allows the inverter to operate within its constraints. Take this step by step and solve the simplest most idealistic case first. Then add complexity one at a time. Agile like, okay? |
I think the function should accept an IV curve (calculated with whatever algorithms and complexity the user desires), apply the inverter parameters and logic, and output the operating point. |
The total inverter power limit is probably the most frequent on to be reached and therefore the most important. Temperature de-rating is no more complicated: it just reduces the inverter total power limit value. Typically the sum of individual MPPT current and power limits is greater than the total inverter current and power limits, so if you have multiple inputs, there has to be some logic to manage that. |
Sure, but in this case it would be wise to keep DC current limits and temperature derating in mind as we alter the MPP code, since we intend to add those other capabilities. |
On a related note, how do people generally decide how many IV points to calculate when combining IV curves? |
About 10,000 points seems reasonable. I tend to concentrate them around knees like the max power point. |
That's a point every 10mV. I think one could get a satisfactory curve with 1000 points, or even fewer. |
A string could be 1500[V] so 10,000 points evenly spaced is 150[mV], but between Vmp & Voc, current changes very fast. For example, a Longi LR5-72HBD-550M has only 8[V] difference between Vmp & Voc at STC, but changes by 13[A]. If an inverter has strings of 30-qty modules (16.5[kW]), then a 3.3[MW] inverter would have 200 strings, so in a space of 240[V] current changes by 2,600[A]! This region, from Vmp to Voc, will be where off-MPP operation happens, so having a lot of points helps keep current resolution more precise in my opinion. |
Good points, I wasn't thinking about string-level IV curves. |
This is great insight. @mikofski is there a good algorithm for concentrating iv points around the MPPs? We can reduce the amount of points needed by only considering voltages within the inverter operating window (excluding 0V to Vmin), but the above comment from mark definitely helps me understand what sort of bin? size we need between iv points |
Unfortunately I don’t have good advice. In the past I’ve used two log-spaces reflected at Vmp but it’s not perfect. (See |
In my experience the points accumulate in the right places as I build up the composite IV curve, so I never thought much about how many points in total. |
Forgive my ignorance, but I am not sure how to create a composite IV curve without selecting a bunch of reference voltages to calculate current at. A part of me thinks that it might be possible to only combine points on curves that correspond with another curve's MPP, but I am not sure. Appreciate any insights! |
To "add" IV curves for devices in series, I think in terms of drawing horizontal lines, and adding the voltages at each current value. That requires extending each IV curve to currents up to the maximum Isc for all curves. A model for this negative bias part of the curve is thus needed. The I've taken to simply linearly extending the IV curve to the breakdown voltage and then drawing a vertical asymptote. That simplified calculating current at negative voltages and makes the addition of many curves (or time series of many curves) feasible, without a meaningful loss in precision. #1781 is a start at a function to add IV curves. It got hung up on the convergence and performance issues with the bishop88 functions. It would be great to see that pushed forward. |
It seems to me that the "clipping off-mpp operation point calculation" function should be tested against a handful of IV curves ranging from simple to complex and perhaps with a range of discretization. But if the function merely accepts and IV curve as an input, which I believe is the right way forward, then for now these IV curve calculation problems can be solved by hacky one off code instead of robust pvlib implementations. |
Is adding IV curves in scope for this PR? I was thinking the function would only take in a single IV curve then find the operating point that meets the inverters constraints (if not @ MPP) - maybe this is oversimplifying but even multi MPPT inverters have to first handle this individually for each channel right? |
@kurt-rhee perhaps you can start a separate discussion on this topic, if needed. |
Here is a DC-side solution for a single 60-cell PV module that uses One might wonder why I choose to use import numpy
import pvlib.pvsystem
import scipy.constants
import scipy.optimize
photocurrent = 6.2
saturation_current = 1.0e-8
resistance_series = 0.0001
resistance_shunt = 5000
n = 1.1
n_s = 60
k_B_J_per_K = scipy.constants.value("Boltzmann constant")
T_K = scipy.constants.convert_temperature(25.0, "Celsius", "Kelvin")
q_C = scipy.constants.value("elementary charge")
nNsVth = n * n_s * k_B_J_per_K * T_K / q_C
args = (
photocurrent,
saturation_current,
resistance_series,
resistance_shunt,
nNsVth,
)
max_power_point_unconstrained = pvlib.pvsystem.max_power_point(*args)
max_power_derate = 0.95
p_mp_max = max_power_derate*max_power_point_unconstrained["p_mp"]
v_mp_min = max_power_point_unconstrained["v_mp"]
i_mp_ic = 0.9*max_power_derate*max_power_point_unconstrained["i_mp"] # Must be feasible.
# Note closure over args (could try a functools.partial).
def negative_power_partial(current):
"""
Compute negative of power generated by device at specified current.
Designed for use by scipy.optimize.minimize.
"""
return -current * pvlib.pvsystem.v_from_i(current, *args)
# Note closure over args (could try a functools.partial).
def voltage_at_current_partial(current):
return pvlib.pvsystem.v_from_i(current, *args)
nonlinear_constraint_power = scipy.optimize.NonlinearConstraint(
negative_power_partial,
-p_mp_max,
0,
jac='3-point',
keep_feasible=True, # Need ensure ic is feasible if we make this True.
)
nonlinear_constraint_voltage = scipy.optimize.NonlinearConstraint(
voltage_at_current_partial,
v_mp_min,
numpy.inf,
jac='3-point',
keep_feasible=True, # Need ensure ic is feasible if we make this True.
)
sol = scipy.optimize.minimize(
negative_power_partial,
i_mp_ic,
jac='3-point',
method='trust-constr',
constraints=(nonlinear_constraint_power, nonlinear_constraint_voltage),
)
print(
"Maximum power point unconstrained:\n"
f"i_mp={max_power_point_unconstrained['i_mp']}, "
f"p_mp={max_power_point_unconstrained['p_mp']}, "
f"v_mp={max_power_point_unconstrained['v_mp']}."
)
print("")
print(
f"Maximum power point constrained to p_mp <= {p_mp_max} and v_mp >= {v_mp_min} "
f"(max_power_derate={max_power_derate}):\n"
f"i_mp={sol.x[0]}, "
f"p_mp={-sol.fun}, "
f"v_mp={-sol.fun/sol.x[0]}."
) Should output something like:
|
Here's a version that directly incorporates an inverter's AC power limit. This code takes a bit longer to run (~8s) and may need to be optimized to be practical. What's pretty fun is that I think one can incorporate more practical nonlinear constraints at will, such as the MPP DC voltage window, and maximum DC and/or AC current limits. import numpy
import pvlib.pvsystem
import scipy.constants
import scipy.optimize
# module
photocurrent = 7.0
saturation_current = 1.0e-8
resistance_series = 0.0001
resistance_shunt = 5000
n = 1.1
n_s = 60
k_B_J_per_K = scipy.constants.value("Boltzmann constant")
T_K = scipy.constants.convert_temperature(25.0, "Celsius", "Kelvin")
q_C = scipy.constants.value("elementary charge")
nNsVth = n * n_s * k_B_J_per_K * T_K / q_C
module_params = (
photocurrent,
saturation_current,
resistance_series,
resistance_shunt,
nNsVth,
)
# inverter
db = pvlib.pvsystem.retrieve_sam('cecinverter')
inverter = db[db.loc["Pdco"].idxmin()] # Get smallest-capacity inverter.
max_power_point_unconstrained = pvlib.pvsystem.max_power_point(*module_params)
v_mp_min = max_power_point_unconstrained["v_mp"]
i_mp_ic = 0.9*max_power_point_unconstrained["i_mp"] # Must be feasible.
# Note closure over args (could try a functools.partial).
def opposite_dc_power(current):
"""
Compute opposite of power generated by device at specified current.
Designed for use by scipy.optimize.minimize.
"""
return -current * pvlib.pvsystem.v_from_i(current, *module_params)
# Note closure over args (could try a functools.partial).
def ac_power_at_dc_current(current):
"""Designed for use by scipy.optimize.NonlinearConstraint."""
v_dc = pvlib.pvsystem.v_from_i(current, *module_params)
return pvlib.inverter.sandia(v_dc, v_dc*current, inverter)
# Note closure over args (could try a functools.partial).
def dc_voltage_at_dc_current(current):
"""Designed for use by scipy.optimize.NonlinearConstraint."""
return pvlib.pvsystem.v_from_i(current, *module_params)
nonlinear_constraint_ac_power = scipy.optimize.NonlinearConstraint(
ac_power_at_dc_current,
-numpy.inf,
inverter["Paco"], # Could temperature-derate this.
jac='3-point',
keep_feasible=True, # Need to ensure IC is feasible if we make this True.
)
nonlinear_constraint_dc_voltage = scipy.optimize.NonlinearConstraint(
dc_voltage_at_dc_current,
v_mp_min,
numpy.inf,
jac='3-point',
keep_feasible=True, # Need to ensure IC is feasible if we make this True.
)
sol = scipy.optimize.minimize(
opposite_dc_power,
i_mp_ic,
jac='3-point',
method='trust-constr',
constraints=(nonlinear_constraint_ac_power, nonlinear_constraint_dc_voltage),
)
print(
"Maximum power point unconstrained:\n"
f"i_mp={max_power_point_unconstrained['i_mp']}, "
f"p_mp={max_power_point_unconstrained['p_mp']}, "
f"v_mp={max_power_point_unconstrained['v_mp']}."
)
print("")
print(
f"Maximum power point constrained to p_ac <= {inverter['Paco']} and v_mp >= {v_mp_min}:\n"
f"i_mp={sol.x[0]}, "
f"p_mp={-sol.fun}, "
f"v_mp={-sol.fun/sol.x[0]}, "
f"p_ac={ac_power_at_dc_current(sol.x[0])}."
) Should output something like:
|
@markcampanelli do you know if that procedure be vectorized over IV curves? From what I see in the scipy documentation, I think not. The function to be minimized has to return |
One thing that's missing from this feature request is the motivation. How important is it to know this voltage? |
@markcampanelli's code sample gave me some inspiration in how to think about the various limits that inverters apply. There are limits on DC current and voltage, and on AC current which are usually thought of as limits on temperature and AC power. Limits on DC current and DC voltage:
Importantly, DC constraints apply independently to each MPPT. Limits on AC current:
The AC power limit applies to the whole inverter. It is not clear how a power derate is apportioned among several MPPTs. All of these limits are likely implemented in the inverter controls, rather than in hardware, because the response to each is to shift the DC voltage. It is not clear if the constraints are applied in sequence or simultaneously. But I don't think that matters for performance modeling; we're interested in the voltage-current-power after all these constraints are applied. The DC current and voltage limits are simple constraints on the domain for voltage and current, which define a segment of the IV curve. The AC power limit affects which point is selected within the IV curve segment. Because there is no inverter temperature model (AFAIK), and the relationship between inverter temperature and output AC current is unknown, this constraint should be set aside for now. But if the pvlib function that determines the operating point can accept a power limit constraint, adding a constraint on AC current for temperature shouldn't be difficult. |
@cwhanse I did not look into vectorization, but I would not be surprised if you are correct that this more complicated solver is not vectorized "out of the box". I suppose this was part of the "performance" items I said were left to investigate. I agree that there are opportunities here to implement several additional inverter constraints, and I'm excited to see what you can come up with :)! My thoughts were along the lines of some sort of a higher-level "mppt string-input computation" function that:
In this fashion, we would be setting up an adaptable interface that (hopefully) would accommodate a "mix and match" of module IV and inverter DC-to-AC models to compute string inputs to MPPTs. I think this might even be fairly easy to extend to a mismatched case, as I sketched out in #1923. Also, one tricky case (which may be a rounding error in the big picture) is the "turn on DC voltage" for the MPPT that may be different than the lower end of the voltage window. I cannot think of how to catch this other than with a post-processing filter that looks at the entire time-series of voltages. More generally, there might need to be a filtering step that lives AFTER the power-maximization function. Lastly, sorry for another big code listing here, but I simplified my above code a bit to show how some nonlinear constraints can be combined (see esp. import numpy
import pvlib.pvsystem
import scipy.constants
import scipy.optimize
# module
photocurrent = 7.0
saturation_current = 1.0e-8
resistance_series = 0.0001
resistance_shunt = 5000
n = 1.1
n_s = 60
k_B_J_per_K = scipy.constants.value("Boltzmann constant")
T_K = scipy.constants.convert_temperature(25.0, "Celsius", "Kelvin")
q_C = scipy.constants.value("elementary charge")
nNsVth = n * n_s * k_B_J_per_K * T_K / q_C
module_params = (
photocurrent,
saturation_current,
resistance_series,
resistance_shunt,
nNsVth,
)
# inverter
db = pvlib.pvsystem.retrieve_sam('cecinverter')
inverter = db[db.loc["Pdco"].idxmin()] # Get smallest-capacity inverter.
max_power_point_unconstrained = pvlib.pvsystem.max_power_point(*module_params)
v_mp_min = max_power_point_unconstrained["v_mp"]
i_mp_ic = 0.9*max_power_point_unconstrained["i_mp"] # Must be feasible.
# Note closure over args (could try a functools.partial).
def opposite_dc_power(current):
"""
Compute opposite of power generated by device at specified DC current.
Designed for use by scipy.optimize.minimize.
"""
return -current * pvlib.pvsystem.v_from_i(current, *module_params)
# Note closure over args (could try a functools.partial).
def ac_power_and_dc_voltage_at_dc_current(current):
"""
Compute inverter-constrained quantities at specified DC current.
Designed for use by scipy.optimize.NonlinearConstraint.
"""
v_dc = pvlib.pvsystem.v_from_i(current, *module_params)
return numpy.concat((pvlib.inverter.sandia(v_dc, v_dc*current, inverter), v_dc))
nonlinear_constraint = scipy.optimize.NonlinearConstraint(
ac_power_and_dc_voltage_at_dc_current,
(0, v_mp_min),
(inverter["Paco"], numpy.inf), # Could temperature-derate Paco.
jac='3-point',
keep_feasible=True, # Need to ensure IC is feasible if we make this True.
)
sol = scipy.optimize.minimize(
opposite_dc_power,
i_mp_ic,
jac='3-point',
method='trust-constr',
constraints=nonlinear_constraint,
)
print(
"Maximum power point unconstrained:\n"
f"i_mp={max_power_point_unconstrained['i_mp']}, "
f"p_mp={max_power_point_unconstrained['p_mp']}, "
f"v_mp={max_power_point_unconstrained['v_mp']}."
)
print("")
print(
f"Maximum power point constrained to p_ac <= {inverter['Paco']} and v_mp >= {v_mp_min}:\n"
f"i_mp={sol.x[0]}, "
f"p_mp={-sol.fun}, "
f"v_mp={-sol.fun/sol.x[0]}, "
f"p_ac={pvlib.inverter.sandia(-sol.fun/sol.x[0], -sol.fun/sol.x[0]*sol.x[0], inverter)}."
) |
This could be a chance to use SciPy Cython Optimize toolbox. Performance can be 30x native |
Continuation of my comment about constraints: finding the maximum power point subject to all three types of constraint (DC voltage, DC current and AC power) is, I think, a convex optimization, when the IV curve does not have mismatch features. For one MPPT, finding the maximum DC power point is a convex optimization problem: the downward concave shape of an IV curve is convex, and the feasible set of solutions (defined by the above linear constraints) is also convex. The inverter operating point is not synonymous with the maximum power point: the difference is, the inverter operating point is constrained by limits on DC voltage, DC current, AC current and AC power. So shift terminology here. The standard form of the optimization problem for the inverter operating point, with DC constraints, is: Minimize f(Vdc) = - I(Vdc) * Vdc subject to: For one MPPT, finding the inverter operating point that also respects the AC power limit may be convex if the inverter model satisfies certain conditions (convex and monotone). I think the Sandia model satisfies these conditions for most parameter sets, but this would need confirmation. If so, the operating point problem in standard form is Minimize g(Vdc) = -inverter(I(Vdc), Vdc)) subject to: Vdc <= Vmax For multiple MPPTs, we need an assumption for how an inverter would allocate AC power reductions among the MPPTs. Because power is additive over MPPTs, I still have hope that the optimization problem will be convex. Because each timestep is independently solved, vectorization is only a matter of adding rows to a design matrix. Why focus on convexity? Because convex optimization is an extensively researched topic, and large-scale optimizers are available. Is there a optimizer that is free to use and easy to integrate with pvlib? I don't know, yet. |
Just wondering, are you thinking you may not need to do the optimization when the IV curve has mismatch features, or just putting that challenge off until later? |
This. It may be that the Vdc domain could be divided into subsets within which the problem is convex, or that may prove impractical. |
There are tools in SciPy optimize for convex optimization, for example |
Those are root finders and minimizers for scalar functions, which don't leverage the convexity of the domain or the objective function. Only the Newton method has been natively vectorized (as you know!) |
I haven't fully digested the recent comments here, but when it comes to an extension to MPPT tracking, it seems that we could start to wade into a situation where we often do not know the "rules" of how any particular inverter will actually track a mismatched string as the weather evolves on any given day. So, the question becomes: Can we come up with a reasonable approximation that "overall" does better than the matched assumption? Indeed, I view the apparent fact that inverters generally make DC voltage larger in order to limit AC voltage one of these "rules". |
What do you mean by the "matched assumption"? That series-connected devices conduct a common current? |
https://docs.scipy.org/doc/scipy-1.15.0/reference/generated/scipy.optimize.fsolve.html Under the hood it calls the FORTRAN minpack library |
I think we may mean different things by "convex." A convex optimization problem has both a convex objective (loss) function and a convex feasible set of solutions. |
Actually, the feasible set of solutions may not be convex with a maximum power constraint because there can be a feasible solution at a high voltage and low current as well as at low voltage and high current. |
Status update for everyone, I got asked to work on a different project so I am going to have to put this down for now. Will hopefully return in the future. |
Feature request
An algorithm that determines the off-mpp operating point for inverters that are clipping
Describe the solution you'd like
Assuming that if the converted AC out is greater than rated AC (at the ambient temperature), then the inverter will increase the voltage until the AC output, what is the DC output, DC voltage, and inverter efficiency loss
Describe alternatives you've considered
I scripted something really, REALLY basic, I don't suggest something as simplistic as this:
But it would be better to redo this with a scalar root finder from
scipy.optimize
likebrentq
.Also, it the AC rated power should be adjusted depending on the ambient temperature, but this could be separate.
Additional context
Sorry, I couldn't find any related issues/prs?
ICYMI: @abhisheksparikh
The text was updated successfully, but these errors were encountered: