|
9 | 9 | """ |
10 | 10 |
|
11 | 11 | import numpy as np |
| 12 | +import pandas as pd |
12 | 13 | from scipy.optimize import curve_fit |
13 | | -from scipy.special import exp10 |
| 14 | +from scipy.special import exp10, lambertw |
14 | 15 |
|
15 | 16 |
|
16 | 17 | def pvefficiency_adr(effective_irradiance, temp_cell, |
@@ -394,3 +395,131 @@ def huld(effective_irradiance, temp_mod, pdc0, k=None, cell_type=None, |
394 | 395 | k[5] * tprime**2 |
395 | 396 | ) |
396 | 397 | return pdc |
| 398 | + |
| 399 | + |
| 400 | +def batzelis(effective_irradiance, temp_cell, |
| 401 | + v_mp, i_mp, v_oc, i_sc, alpha_sc, beta_voc): |
| 402 | + """ |
| 403 | + Compute maximum power point, open circuit, and short circuit |
| 404 | + values using Batzelis's method. |
| 405 | +
|
| 406 | + Batzelis's method (described in Section III of [1]_) is a fast method |
| 407 | + of computing the maximum power current and voltage. The calculations |
| 408 | + are rooted in the De Soto single-diode model, but require only typical |
| 409 | + datasheet information. |
| 410 | +
|
| 411 | + Parameters |
| 412 | + ---------- |
| 413 | + effective_irradiance : numeric, non-negative |
| 414 | + Effective irradiance incident on the PV module. [Wm⁻²] |
| 415 | + temp_cell : numeric |
| 416 | + PV module operating temperature. [°C] |
| 417 | + v_mp : float |
| 418 | + Maximum power point voltage at STC. [V] |
| 419 | + i_mp : float |
| 420 | + Maximum power point current at STC. [A] |
| 421 | + v_oc : float |
| 422 | + Open-circuit voltage at STC. [V] |
| 423 | + i_sc : float |
| 424 | + Short-circuit current at STC. [A] |
| 425 | + alpha_sc : float |
| 426 | + Short-circuit current temperature coefficient at STC. [A/K] |
| 427 | + beta_voc : float |
| 428 | + Open-circuit voltage temperature coefficient at STC. [V/K] |
| 429 | +
|
| 430 | + Returns |
| 431 | + ------- |
| 432 | + dict |
| 433 | + The returned dict-like object contains the keys/columns: |
| 434 | +
|
| 435 | + * ``p_mp`` - power at maximum power point. [W] |
| 436 | + * ``i_mp`` - current at maximum power point. [A] |
| 437 | + * ``v_mp`` - voltage at maximum power point. [V] |
| 438 | + * ``i_sc`` - short circuit current. [A] |
| 439 | + * ``v_oc`` - open circuit voltage. [V] |
| 440 | +
|
| 441 | + Notes |
| 442 | + ----- |
| 443 | + This method is the combination of three sub-methods for: |
| 444 | +
|
| 445 | + 1. estimating single-diode model parameters from datasheet information |
| 446 | + 2. translating SDM parameters from STC to operating conditions |
| 447 | + (taken from the De Soto model) |
| 448 | + 3. estimating the MPP, OC, and SC points on the resulting I-V curve. |
| 449 | +
|
| 450 | + At extremely low irradiance (e.g. 1e-10 Wm⁻²), this model can produce |
| 451 | + negative voltages. This function clips any negative voltages to zero. |
| 452 | +
|
| 453 | + References |
| 454 | + ---------- |
| 455 | + .. [1] E. I. Batzelis, "Simple PV Performance Equations Theoretically Well |
| 456 | + Founded on the Single-Diode Model," Journal of Photovoltaics vol. 7, |
| 457 | + no. 5, pp. 1400-1409, Sep 2017, :doi:`10.1109/JPHOTOV.2017.2711431` |
| 458 | +
|
| 459 | + Examples |
| 460 | + -------- |
| 461 | + >>> params = {'i_sc': 15.98, 'v_oc': 50.26, 'i_mp': 15.27, 'v_mp': 42.57, |
| 462 | + ... 'alpha_sc': 0.007351, 'beta_voc': -0.120624} |
| 463 | + >>> batzelis(np.array([1000, 800]), np.array([25, 30]), **params) |
| 464 | + {'p_mp': array([650.0439 , 512.99199048]), |
| 465 | + 'i_mp': array([15.27 , 12.23049303]), |
| 466 | + 'v_mp': array([42.57 , 41.94368856]), |
| 467 | + 'i_sc': array([15.98 , 12.813404]), |
| 468 | + 'v_oc': array([50.26 , 49.26532902])} |
| 469 | + """ |
| 470 | + # convert temp coeffs from A/K and V/K to 1/K |
| 471 | + alpha_sc = alpha_sc / i_sc |
| 472 | + beta_voc = beta_voc / v_oc |
| 473 | + |
| 474 | + t0 = 298.15 |
| 475 | + delT = temp_cell - (t0 - 273.15) |
| 476 | + lamT = (temp_cell + 273.15) / t0 |
| 477 | + g = effective_irradiance / 1000 |
| 478 | + # for zero/negative irradiance, use lnG=large negative number so that |
| 479 | + # computed voltages are negative and then clipped to zero |
| 480 | + with np.errstate(divide='ignore'): # needed for pandas for some reason |
| 481 | + lnG = np.log(g, out=np.full_like(g, -9e9), where=(g > 0)) |
| 482 | + lnG = np.where(np.isfinite(g), lnG, np.nan) # also preserve nans |
| 483 | + |
| 484 | + # Eq 9-10 |
| 485 | + del0 = (1 - beta_voc * t0) / (50.1 - alpha_sc * t0) |
| 486 | + w0 = np.real(lambertw(np.exp(1/del0 + 1))) |
| 487 | + |
| 488 | + # Eqs 27-28 |
| 489 | + alpha_imp = alpha_sc + (beta_voc - 1/t0) / (w0 - 1) |
| 490 | + beta_vmp = (v_oc / v_mp) * ( |
| 491 | + beta_voc / (1 + del0) + |
| 492 | + (del0 * (w0 - 1) - 1/(1 + del0)) / t0 |
| 493 | + ) |
| 494 | + |
| 495 | + # Eq 26 |
| 496 | + eps0 = (del0 / (1 + del0)) * (v_oc / v_mp) |
| 497 | + eps1 = del0 * (w0 - 1) * (v_oc / v_mp) - 1 |
| 498 | + |
| 499 | + # Eqs 22-25 |
| 500 | + isc = g * i_sc * (1 + alpha_sc * delT) |
| 501 | + voc = v_oc * (1 + del0 * lamT * lnG + beta_voc * delT) |
| 502 | + imp = g * i_mp * (1 + alpha_imp * delT) |
| 503 | + vmp = v_mp * (1 + eps0 * lamT * lnG + eps1 * (1 - g) + beta_vmp * delT) |
| 504 | + |
| 505 | + # handle negative voltages from zero and extremely small irradiance |
| 506 | + vmp = np.clip(vmp, a_min=0, a_max=None) |
| 507 | + voc = np.clip(voc, a_min=0, a_max=None) |
| 508 | + |
| 509 | + out = { |
| 510 | + 'p_mp': vmp * imp, |
| 511 | + 'i_mp': imp, |
| 512 | + 'v_mp': vmp, |
| 513 | + 'i_sc': isc, |
| 514 | + 'v_oc': voc, |
| 515 | + } |
| 516 | + |
| 517 | + # if pandas in, ensure pandas out |
| 518 | + pandas_inputs = [ |
| 519 | + x for x in [effective_irradiance, temp_cell] |
| 520 | + if isinstance(x, pd.Series) |
| 521 | + ] |
| 522 | + if pandas_inputs: |
| 523 | + out = pd.DataFrame(out, index=pandas_inputs[0].index) |
| 524 | + |
| 525 | + return out |
0 commit comments