|
14 | 14 | _extract_sdm_params, _initial_iv_params, _update_iv_params
|
15 | 15 | )
|
16 | 16 |
|
| 17 | +from pvlib.pvsystem import ( |
| 18 | + _pvsyst_Rsh, _pvsyst_IL, _pvsyst_Io, _pvsyst_nNsVth, _pvsyst_gamma |
| 19 | +) |
17 | 20 |
|
18 | 21 | CONSTANTS = {'E0': 1000.0, 'T0': 25.0, 'k': constants.k, 'q': constants.e}
|
19 | 22 |
|
@@ -305,3 +308,323 @@ def maxp(temp_cell, irrad_ref, alpha_sc, gamma_ref, mu_gamma, I_L_ref,
|
305 | 308 | gamma_pdc = _first_order_centered_difference(maxp, x0=temp_ref, args=args)
|
306 | 309 |
|
307 | 310 | return gamma_pdc / pmp
|
| 311 | + |
| 312 | + |
| 313 | +def fit_pvsyst_iec61853_sandia_2025(effective_irradiance, temp_cell, |
| 314 | + i_sc, v_oc, i_mp, v_mp, |
| 315 | + cells_in_series, EgRef=1.121, |
| 316 | + alpha_sc=None, beta_mp=None, |
| 317 | + R_s=None, r_sh_coeff=0.12, |
| 318 | + min_Rsh_irradiance=None, |
| 319 | + irradiance_tolerance=20, |
| 320 | + temperature_tolerance=1): |
| 321 | + """ |
| 322 | + Estimate parameters for the PVsyst module performance model using |
| 323 | + IEC 61853-1 matrix measurements. |
| 324 | +
|
| 325 | + Parameters |
| 326 | + ---------- |
| 327 | + effective_irradiance : array |
| 328 | + Effective irradiance for each test condition [W/m²] |
| 329 | + temp_cell : array |
| 330 | + Cell temperature for each test condition [C] |
| 331 | + i_sc : array |
| 332 | + Short circuit current for each test condition [A] |
| 333 | + v_oc : array |
| 334 | + Open circuit voltage for each test condition [V] |
| 335 | + i_mp : array |
| 336 | + Current at maximum power point for each test condition [A] |
| 337 | + v_mp : array |
| 338 | + Voltage at maximum power point for each test condition [V] |
| 339 | + cells_in_series : int |
| 340 | + The number of cells connected in series. |
| 341 | + EgRef : float, optional |
| 342 | + The energy bandgap at reference temperature in units of eV. |
| 343 | + 1.121 eV for crystalline silicon. EgRef must be >0. |
| 344 | + alpha_sc : float, optional |
| 345 | + Temperature coefficient of short circuit current. If not specified, |
| 346 | + it will be estimated using the ``i_sc`` values at irradiance of |
| 347 | + 1000 W/m2. [A/K] |
| 348 | + beta_mp : float, optional |
| 349 | + Temperature coefficient of maximum power voltage. If not specified, |
| 350 | + it will be estimated using the ``v_mp`` values at irradiance of |
| 351 | + 1000 W/m2. [1/K] |
| 352 | + R_s : float, optional |
| 353 | + Series resistance value. If not provided, a value will be estimated |
| 354 | + from the input measurements. [ohm] |
| 355 | + r_sh_coeff : float, default 0.12 |
| 356 | + Shunt resistance fitting coefficient. The default value is taken |
| 357 | + from [1]_. |
| 358 | + min_Rsh_irradiance : float, optional |
| 359 | + Irradiance threshold below which values are excluded when estimating |
| 360 | + shunt resistance parameter values. May be useful for modules |
| 361 | + with problematic low-light measurements. [W/m²] |
| 362 | + irradiance_tolerance : float, default 20 |
| 363 | + Tolerance for irradiance variation around the STC value. |
| 364 | + The default value corresponds to a +/- 2% interval around the STC |
| 365 | + value of 1000 W/m². [W/m²] |
| 366 | + temperature_tolerance : float, default 1 |
| 367 | + Tolerance for temperature variation around the STC value. |
| 368 | + The default value corresponds to a +/- 1 degree interval around the STC |
| 369 | + value of 25 degrees. [C] |
| 370 | +
|
| 371 | + Returns |
| 372 | + ------- |
| 373 | + dict |
| 374 | + alpha_sc : float |
| 375 | + short circuit current temperature coefficient [A/K] |
| 376 | + gamma_ref : float |
| 377 | + diode (ideality) factor at STC [unitless] |
| 378 | + mu_gamma : float |
| 379 | + temperature coefficient for diode (ideality) factor [1/K] |
| 380 | + I_L_ref : float |
| 381 | + light current at STC [A] |
| 382 | + I_o_ref : float |
| 383 | + dark current at STC [A] |
| 384 | + R_sh_ref : float |
| 385 | + shunt resistance at STC [ohm] |
| 386 | + R_sh_0 : float |
| 387 | + shunt resistance at zero irradiance [ohm] |
| 388 | + R_sh_exp : float |
| 389 | + exponential factor defining decrease in shunt resistance with |
| 390 | + increasing effective irradiance |
| 391 | + R_s : float |
| 392 | + series resistance at STC [ohm] |
| 393 | + cells_in_series : int |
| 394 | + number of cells in series |
| 395 | + EgRef : float |
| 396 | + effective band gap at STC [eV] |
| 397 | +
|
| 398 | + See also |
| 399 | + -------- |
| 400 | + pvlib.pvsystem.calcparams_pvsyst |
| 401 | + pvlib.ivtools.sdm.fit_pvsyst_sandia |
| 402 | +
|
| 403 | + Notes |
| 404 | + ----- |
| 405 | + Input arrays of operating conditions and electrical measurements must be |
| 406 | + 1-D with equal lengths. |
| 407 | +
|
| 408 | + Values supplied for ``alpha_sc``, ``beta_mp``, and ``R_s`` must be |
| 409 | + consistent with the matrix data, as these values are used when estimating |
| 410 | + other model parameters. |
| 411 | +
|
| 412 | + This method is non-iterative. In some cases, it may be desirable to |
| 413 | + refine the estimated parameter values using a numerical optimizer such as |
| 414 | + the default method in ``scipy.optimize.minimize``. |
| 415 | +
|
| 416 | + References |
| 417 | + ---------- |
| 418 | + .. [1] K. S. Anderson, C. W. Hansen, and M. Theristis, "A Noniterative |
| 419 | + Method of Estimating Parameter Values for the PVsyst Version 6 |
| 420 | + Single-Diode Model From IEC 61853-1 Matrix Measurements," IEEE Journal |
| 421 | + of Photovoltaics, vol. 15, 3, 2025. :doi:`10.1109/JPHOTOV.2025.3554338` |
| 422 | + """ |
| 423 | + |
| 424 | + is_g_stc = np.isclose(effective_irradiance, 1000, rtol=0, |
| 425 | + atol=irradiance_tolerance) |
| 426 | + is_t_stc = np.isclose(temp_cell, 25, rtol=0, |
| 427 | + atol=temperature_tolerance) |
| 428 | + |
| 429 | + if alpha_sc is None: |
| 430 | + mu_i_sc = _fit_tempco_pvsyst_iec61853_sandia_2025(i_sc[is_g_stc], |
| 431 | + temp_cell[is_g_stc]) |
| 432 | + i_sc_ref = float(i_sc[is_g_stc & is_t_stc].item()) |
| 433 | + alpha_sc = mu_i_sc * i_sc_ref |
| 434 | + |
| 435 | + if beta_mp is None: |
| 436 | + beta_mp = _fit_tempco_pvsyst_iec61853_sandia_2025(v_mp[is_g_stc], |
| 437 | + temp_cell[is_g_stc]) |
| 438 | + |
| 439 | + R_sh_ref, R_sh_0, R_sh_exp = \ |
| 440 | + _fit_shunt_resistances_pvsyst_iec61853_sandia_2025( |
| 441 | + i_sc, i_mp, v_mp, effective_irradiance, temp_cell, beta_mp, |
| 442 | + coeff=r_sh_coeff, min_irradiance=min_Rsh_irradiance) |
| 443 | + |
| 444 | + if R_s is None: |
| 445 | + R_s = _fit_series_resistance_pvsyst_iec61853_sandia_2025(v_oc, i_mp, |
| 446 | + v_mp) |
| 447 | + |
| 448 | + gamma_ref, mu_gamma = \ |
| 449 | + _fit_diode_ideality_factor_pvsyst_iec61853_sandia_2025( |
| 450 | + i_sc[is_t_stc], v_oc[is_t_stc], i_mp[is_t_stc], v_mp[is_t_stc], |
| 451 | + effective_irradiance[is_t_stc], temp_cell[is_t_stc], |
| 452 | + R_sh_ref, R_sh_0, R_sh_exp, R_s, cells_in_series) |
| 453 | + |
| 454 | + I_o_ref = _fit_saturation_current_pvsyst_iec61853_sandia_2025( |
| 455 | + i_sc, v_oc, effective_irradiance, temp_cell, gamma_ref, mu_gamma, |
| 456 | + R_sh_ref, R_sh_0, R_sh_exp, R_s, cells_in_series, EgRef |
| 457 | + ) |
| 458 | + |
| 459 | + I_L_ref = _fit_photocurrent_pvsyst_iec61853_sandia_2025( |
| 460 | + i_sc, effective_irradiance, temp_cell, alpha_sc, |
| 461 | + gamma_ref, mu_gamma, |
| 462 | + I_o_ref, R_sh_ref, R_sh_0, R_sh_exp, R_s, cells_in_series, EgRef |
| 463 | + ) |
| 464 | + |
| 465 | + gamma_ref, mu_gamma = \ |
| 466 | + _fit_diode_ideality_factor_post_pvsyst_iec61853_sandia_2025( |
| 467 | + i_mp, v_mp, effective_irradiance, temp_cell, alpha_sc, I_L_ref, |
| 468 | + I_o_ref, R_sh_ref, R_sh_0, R_sh_exp, R_s, cells_in_series, EgRef) |
| 469 | + |
| 470 | + fitted_params = dict( |
| 471 | + alpha_sc=alpha_sc, |
| 472 | + gamma_ref=gamma_ref, |
| 473 | + mu_gamma=mu_gamma, |
| 474 | + I_L_ref=I_L_ref, |
| 475 | + I_o_ref=I_o_ref, |
| 476 | + R_sh_ref=R_sh_ref, |
| 477 | + R_sh_0=R_sh_0, |
| 478 | + R_sh_exp=R_sh_exp, |
| 479 | + R_s=R_s, |
| 480 | + cells_in_series=cells_in_series, |
| 481 | + EgRef=EgRef, |
| 482 | + ) |
| 483 | + return fitted_params |
| 484 | + |
| 485 | + |
| 486 | +def _fit_tempco_pvsyst_iec61853_sandia_2025(values, temp_cell, |
| 487 | + temp_cell_ref=25): |
| 488 | + fit = np.polynomial.polynomial.Polynomial.fit(temp_cell, values, deg=1) |
| 489 | + intercept, slope = fit.convert().coef |
| 490 | + value_ref = intercept + slope*temp_cell_ref |
| 491 | + return slope / value_ref |
| 492 | + |
| 493 | + |
| 494 | +def _fit_shunt_resistances_pvsyst_iec61853_sandia_2025( |
| 495 | + i_sc, i_mp, v_mp, effective_irradiance, temp_cell, |
| 496 | + beta_v_mp, coeff=0.2, min_irradiance=None): |
| 497 | + if min_irradiance is None: |
| 498 | + min_irradiance = 0 |
| 499 | + |
| 500 | + mask = effective_irradiance >= min_irradiance |
| 501 | + i_sc = i_sc[mask] |
| 502 | + i_mp = i_mp[mask] |
| 503 | + v_mp = v_mp[mask] |
| 504 | + effective_irradiance = effective_irradiance[mask] |
| 505 | + temp_cell = temp_cell[mask] |
| 506 | + |
| 507 | + # Equation 10 |
| 508 | + Rsh_est = ( |
| 509 | + (v_mp / (1 + beta_v_mp * (temp_cell - 25))) |
| 510 | + / (coeff * (i_sc - i_mp)) |
| 511 | + ) |
| 512 | + Rshexp = 5.5 |
| 513 | + |
| 514 | + # Eq 11 |
| 515 | + y = Rsh_est |
| 516 | + x = np.exp(-Rshexp * effective_irradiance / 1000) |
| 517 | + |
| 518 | + fit = np.polynomial.polynomial.Polynomial.fit(x, y, deg=1) |
| 519 | + intercept, slope = fit.convert().coef |
| 520 | + Rshbase = intercept |
| 521 | + Rsh0 = slope + Rshbase |
| 522 | + |
| 523 | + # Eq 12 |
| 524 | + expRshexp = np.exp(-Rshexp) |
| 525 | + Rshref = Rshbase * (1 - expRshexp) + Rsh0 * expRshexp |
| 526 | + |
| 527 | + return Rshref, Rsh0, Rshexp |
| 528 | + |
| 529 | + |
| 530 | +def _fit_series_resistance_pvsyst_iec61853_sandia_2025(v_oc, i_mp, v_mp): |
| 531 | + # Stein et al 2014, https://doi.org/10.1109/PVSC.2014.6925326 |
| 532 | + |
| 533 | + # Eq 13 |
| 534 | + x = np.array([np.ones(len(i_mp)), i_mp, np.log(i_mp), v_mp]).T |
| 535 | + y = v_oc |
| 536 | + |
| 537 | + coeff, _, _, _ = np.linalg.lstsq(x, y, rcond=None) |
| 538 | + R_s = coeff[1] |
| 539 | + return R_s |
| 540 | + |
| 541 | + |
| 542 | +def _fit_diode_ideality_factor_pvsyst_iec61853_sandia_2025( |
| 543 | + i_sc, v_oc, i_mp, v_mp, effective_irradiance, temp_cell, |
| 544 | + R_sh_ref, R_sh_0, R_sh_exp, R_s, cells_in_series): |
| 545 | + |
| 546 | + NsVth = _pvsyst_nNsVth(temp_cell, gamma=1, cells_in_series=cells_in_series) |
| 547 | + Rsh = _pvsyst_Rsh(effective_irradiance, R_sh_ref, R_sh_0, R_sh_exp) |
| 548 | + term1 = (i_sc * (1 + R_s/Rsh) - v_oc / Rsh) # Eq 15 |
| 549 | + term2 = (i_sc - i_mp) * (1 + R_s/Rsh) - v_mp / Rsh # Eq 16 |
| 550 | + |
| 551 | + # Eq 14 |
| 552 | + x1 = NsVth * np.log(term2 / term1) |
| 553 | + |
| 554 | + x = np.array([x1]).T |
| 555 | + y = v_mp + i_mp*R_s - v_oc |
| 556 | + |
| 557 | + coeff, _, _, _ = np.linalg.lstsq(x, y, rcond=None) |
| 558 | + gamma_ref = coeff[0] |
| 559 | + return gamma_ref, 0 |
| 560 | + |
| 561 | + |
| 562 | +def _fit_saturation_current_pvsyst_iec61853_sandia_2025( |
| 563 | + i_sc, v_oc, effective_irradiance, temp_cell, gamma_ref, mu_gamma, |
| 564 | + R_sh_ref, R_sh_0, R_sh_exp, R_s, cells_in_series, EgRef): |
| 565 | + R_sh = _pvsyst_Rsh(effective_irradiance, R_sh_ref, R_sh_0, R_sh_exp) |
| 566 | + gamma = _pvsyst_gamma(temp_cell, gamma_ref, mu_gamma) |
| 567 | + nNsVth = _pvsyst_nNsVth(temp_cell, gamma, cells_in_series) |
| 568 | + |
| 569 | + # Eq 17 |
| 570 | + I_o_est = (i_sc * (1 + R_s/R_sh) - v_oc/R_sh) / (np.expm1(v_oc / nNsVth)) |
| 571 | + x = _pvsyst_Io(temp_cell, gamma, I_o_ref=1, EgRef=EgRef) |
| 572 | + |
| 573 | + # Eq 18 |
| 574 | + log_I_o_ref = np.mean(np.log(I_o_est) - np.log(x)) |
| 575 | + I_o_ref = np.exp(log_I_o_ref) |
| 576 | + |
| 577 | + return I_o_ref |
| 578 | + |
| 579 | + |
| 580 | +def _fit_photocurrent_pvsyst_iec61853_sandia_2025( |
| 581 | + i_sc, effective_irradiance, temp_cell, alpha_sc, gamma_ref, |
| 582 | + mu_gamma, I_o_ref, R_sh_ref, R_sh_0, R_sh_exp, R_s, cells_in_series, |
| 583 | + EgRef): |
| 584 | + R_sh = _pvsyst_Rsh(effective_irradiance, R_sh_ref, R_sh_0, R_sh_exp) |
| 585 | + gamma = _pvsyst_gamma(temp_cell, gamma_ref, mu_gamma) |
| 586 | + I_o = _pvsyst_Io(temp_cell, gamma, I_o_ref, EgRef) |
| 587 | + nNsVth = _pvsyst_nNsVth(temp_cell, gamma, cells_in_series) |
| 588 | + |
| 589 | + # Eq 19 |
| 590 | + I_L_est = i_sc + I_o * (np.expm1(i_sc * R_s / nNsVth)) + i_sc * R_s / R_sh |
| 591 | + |
| 592 | + # Eq 20 |
| 593 | + x = np.array([effective_irradiance / 1000]).T |
| 594 | + y = I_L_est - effective_irradiance / 1000 * alpha_sc * (temp_cell - 25) |
| 595 | + coeff, _, _, _ = np.linalg.lstsq(x, y, rcond=None) |
| 596 | + I_L_ref = coeff[0] |
| 597 | + return I_L_ref |
| 598 | + |
| 599 | + |
| 600 | +def _fit_diode_ideality_factor_post_pvsyst_iec61853_sandia_2025( |
| 601 | + i_mp, v_mp, effective_irradiance, temp_cell, alpha_sc, I_L_ref, |
| 602 | + I_o_ref, R_sh_ref, R_sh_0, R_sh_exp, R_s, cells_in_series, EgRef): |
| 603 | + |
| 604 | + Rsh = _pvsyst_Rsh(effective_irradiance, R_sh_ref, R_sh_0, R_sh_exp) |
| 605 | + I_L = _pvsyst_IL(effective_irradiance, temp_cell, I_L_ref, alpha_sc) |
| 606 | + NsVth = _pvsyst_nNsVth(temp_cell, gamma=1, cells_in_series=cells_in_series) |
| 607 | + |
| 608 | + Tref_K = 25 + 273.15 |
| 609 | + Tcell_K = temp_cell + 273.15 |
| 610 | + |
| 611 | + # Eq 21 |
| 612 | + k = constants.k # Boltzmann constant in J/K |
| 613 | + q = constants.e # elementary charge in coulomb |
| 614 | + numerator = ( |
| 615 | + (q * EgRef / k) * (1/Tref_K - 1/Tcell_K) |
| 616 | + + (v_mp + i_mp*R_s) / NsVth |
| 617 | + ) |
| 618 | + denominator = ( |
| 619 | + np.log((I_L - i_mp - (v_mp+i_mp*R_s) / Rsh) / I_o_ref) |
| 620 | + - 3 * np.log(Tcell_K / Tref_K) |
| 621 | + ) |
| 622 | + gamma_est = numerator / denominator |
| 623 | + |
| 624 | + # Eq 22 |
| 625 | + x = np.array([np.ones(len(i_mp)), temp_cell - 25]).T |
| 626 | + y = gamma_est |
| 627 | + |
| 628 | + coeff, _, _, _ = np.linalg.lstsq(x, y, rcond=None) |
| 629 | + gamma_ref, mu_gamma = coeff |
| 630 | + return gamma_ref, mu_gamma |
0 commit comments