Skip to content

Instantly share code, notes, and snippets.

@mikofski
Last active March 13, 2020 19:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mikofski/df318d1f892767ac7c762e732fecaa7f to your computer and use it in GitHub Desktop.
Save mikofski/df318d1f892767ac7c762e732fecaa7f to your computer and use it in GitHub Desktop.
proof of an implicit solver
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Example of implicit approach to solving for current of a PV system given voltage\n",
"\n",
"This example is part of a series of notebooks that explore using an implicit approach to solving a PV system versus an explicit approach. Specifically this example finds the current given a specified system voltage.\n",
"\n",
"First set up the notebook to make plots inline, then import NumPy to vector math, and the solvers we're going to use from SciPy optimize package."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"# imports\n",
"%matplotlib inline\n",
"\n",
"import numpy as np\n",
"from scipy.optimize import minimize_scalar, fsolve\n",
"# NOTE: fsolve is the same as root() set to 'hybr'\n",
"from matplotlib import pyplot as plt"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"# CONSTANTS\n",
"IL = 7.0 # [A] photogenerated current\n",
"I0 = 1.0e-6 # [A] dark current\n",
"RSH = 20.0 # [ohms] shunt resistance\n",
"RS = 0.001 # [ohms] series resistance\n",
"GAMMA = 1.23 # diode ideality\n",
"VT = 0.026 # [V] thermal voltage at 300[K]\n",
"VBYPASS = -0.5 # bypass diode trigger voltage\n",
"VSYS = 240.0 # [V] system voltage\n",
"NSERIES_CELLS = 72 # number of series cells per module\n",
"NMODS = 8 # number of modules per string\n",
"NSTRINGS = 3 # number of strings in system\n",
"NBYPASS = 3 # number of bypass diodes per module, assumes cells divided evenly"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Callback\n",
"\n",
"The SciPy solver takes a callback function which takes a vector, `x`, of solution guesses, and a tuple of extra arguments, `args`, to pass in the independent variables that define the conditions such as number of series cells per module, number of modules per string, number of strings, how many bypass diodes there are per module, and the single diode model parameters."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"def isys_from_voltage(x, *args):\n",
" nseries_cells = args[0] # number of series cells per module\n",
" nmods = args[1] # number of modules per string\n",
" nstrings = args[2] # number of strings\n",
" nbypass = args[3] # number of bypass diodes per module, assumes evenly divided\n",
" # single diode parameters\n",
" il, i0, rsh, rs, gamma, vt, vbypass, vsys = args[4:]\n",
"\n",
" # cells\n",
" idx0 = nseries_cells * nmods * nstrings\n",
" vcells = x[:idx0].reshape(-1, nstrings) # reshape strings into columns\n",
"\n",
" # submodules\n",
" idx1 = idx0 + nbypass * nmods * nstrings\n",
" vsubs = x[idx0:idx1].reshape(-1, nstrings) # reshape strings into columns\n",
"\n",
" # strings\n",
" idx2 = idx1 + nstrings\n",
" istrings = x[idx1:idx2]\n",
"\n",
" # system\n",
" isys = x[-1]\n",
"\n",
" # residuals\n",
" residuals = np.zeros(x.shape)\n",
"\n",
" # cells constraints\n",
" vdiode = vcells + istrings*rs\n",
" # this is the \"single diode model\" of a solar cell\n",
" residuals[:idx0] = np.ravel(\n",
" il - i0*(np.exp(vdiode/gamma/vt) - 1.0) - vdiode/rsh - istrings\n",
" )\n",
"\n",
" # bypass diode constraint\n",
" ncells_bypass = nseries_cells / nbypass\n",
" vsubmodule = np.zeros(vsubs.shape)\n",
" for string in range(nstrings):\n",
" for submodule in range(nbypass * nmods):\n",
" jdx = int(submodule*ncells_bypass)\n",
" vsubmodule[submodule, string] = np.sum(vcells[jdx:int(jdx+ncells_bypass), string])\n",
" vsubmodule[vsubmodule < vbypass] = vbypass\n",
" residuals[idx0:idx1] = np.ravel(vsubmodule - vsubs)\n",
"\n",
" # string constraints\n",
" vstrings = [np.sum(vsubs[:, string]) for string in range(nstrings)]\n",
" residuals[idx1:idx2] = vsys - np.array(vstrings)\n",
"\n",
" # system constraint\n",
" residuals[-1] = np.sum(istrings) - isys\n",
" \n",
" return residuals"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"({'nfev': 1820,\n",
" 'fjac': array([[-9.99624138e-01, 8.01234430e-04, 3.05076907e-05, ...,\n",
" 3.38372362e-15, 2.30579607e-12, 3.92898781e-17],\n",
" [-6.65622409e-05, -9.99613737e-01, 7.59709723e-05, ...,\n",
" 3.38676626e-15, 3.97123134e-12, 4.56848262e-18],\n",
" [-6.08932842e-05, -8.13107856e-05, -9.99345635e-01, ...,\n",
" 3.38782842e-15, -1.29754627e-12, -6.15498768e-16],\n",
" ...,\n",
" [ 1.20318649e-05, 1.21131317e-03, 6.15106427e-04, ...,\n",
" -1.57970479e-01, -1.45660662e-02, -6.34531601e-02],\n",
" [ 2.01752822e-06, 3.11726575e-06, 1.08245364e-03, ...,\n",
" 7.10489987e-02, -1.72674442e-01, -3.75597748e-02],\n",
" [ 1.57046806e-03, 1.57071003e-03, 1.57501669e-03, ...,\n",
" -4.40901236e-02, -4.40884819e-02, 9.21943067e-01]]),\n",
" 'r': array([ 2.32598960e+02, -1.99255704e-01, -1.65809651e-02, ...,\n",
" -7.43576114e-01, 3.77448978e-02, -9.21179634e-01]),\n",
" 'qtf': array([-2.81372496e-10, -1.20942828e-09, 4.83815989e-09, ...,\n",
" 9.01168166e-10, 1.52157082e-09, -1.78711456e-11]),\n",
" 'fvec': array([-1.49120982e-09, -1.43849999e-09, 1.87828331e-09, ...,\n",
" 0.00000000e+00, 2.84217094e-14, 0.00000000e+00])},\n",
" 1,\n",
" 'The solution converged.')"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# make an initial guess\n",
"varb = 0.5 # [V] some voltage - use voc_est with Rsh=np.Inf & Rs=0\n",
"iarb = 6.0 # [A] some current\n",
"v0 = [varb] * (NSTRINGS*NMODS*NSERIES_CELLS) # [V] cell voltages guesses\n",
"vsub0 = [varb * NSERIES_CELLS/NBYPASS] * (NSTRINGS*NMODS*NBYPASS) # [V] submodule voltages guesses\n",
"istr0 = [iarb] * NSTRINGS # [A] string currents guesses\n",
"isys0 = sum(istr0) # [A] initial system current guess\n",
"X0 = np.array(v0 + vsub0 + istr0 + [isys0]) # inital guess\n",
"\n",
"# add some noise to the light current\n",
"noise = np.random.randn(NMODS*NSERIES_CELLS, NSTRINGS) / 1000\n",
"\n",
"# solve it\n",
"ARGS = (NSERIES_CELLS, NMODS, NSTRINGS, NBYPASS, IL+noise,\n",
" I0, RSH, RS, GAMMA, VT, VBYPASS, VSYS)\n",
"full_output = fsolve(isys_from_voltage, x0=X0, args=ARGS, full_output=True)\n",
"# if full output then results is a tuple\n",
"results = full_output[0]\n",
"full_output[1:]"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([[0.41670409, 0.41669829, 0.41659487],\n",
" [0.41668225, 0.41652674, 0.41669965],\n",
" [0.41664967, 0.41669136, 0.41672096],\n",
" ...,\n",
" [0.41668421, 0.41666032, 0.41674502],\n",
" [0.41676059, 0.41670815, 0.41664592],\n",
" [0.41671843, 0.41663004, 0.41672851]])"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"idx0 = NSTRINGS*NMODS*NSERIES_CELLS\n",
"voltages = results[:idx0].reshape(-1, NSTRINGS) # cell voltages\n",
"voltages"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([[10.00008429, 10.00004239, 10.00042427],\n",
" [ 9.99981484, 10.00012778, 9.99970785],\n",
" [ 9.99994136, 9.99952761, 9.99967285],\n",
" [ 9.99995088, 10.00000426, 9.99957088],\n",
" [10.00007806, 9.99988728, 9.99968472],\n",
" [10.00051033, 9.99985692, 9.99959002],\n",
" [ 9.99982495, 9.999643 , 9.99994605],\n",
" [10.00016988, 10.00013181, 9.99999447],\n",
" [ 9.99966196, 10.0005762 , 10.00009929],\n",
" [10.0001879 , 9.99999273, 10.00005471],\n",
" [ 9.999977 , 10.00002123, 10.00033012],\n",
" [10.00004141, 9.99995615, 10.00050979],\n",
" [10.00020996, 9.99947279, 10.0005103 ],\n",
" [ 9.99983156, 10.00009486, 10.00010905],\n",
" [10.0000343 , 10.0004712 , 10.00009353],\n",
" [10.0000557 , 9.99985237, 10.00006917],\n",
" [ 9.99973493, 10.00027908, 10.0004533 ],\n",
" [ 9.99960626, 9.99998118, 10.00001254],\n",
" [ 9.99975987, 9.99996528, 10.00002193],\n",
" [10.00040464, 9.9996972 , 9.99989118],\n",
" [10.0000792 , 9.99978051, 9.99948485],\n",
" [ 9.99966007, 10.00021479, 9.99955789],\n",
" [10.00060179, 10.0004249 , 10.00021889],\n",
" [ 9.99977887, 9.99999847, 9.99999236]])"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"idx1 = idx0 + NSTRINGS*NMODS*NBYPASS\n",
"submodules = results[idx0:idx1].reshape(-1, NSTRINGS)\n",
"submodules"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([6.42207435, 6.42213142, 6.42219736])"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"strings = results[idx1:(idx1+NSTRINGS)]\n",
"strings"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"19.26640313192219"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"system = results[-1]\n",
"system"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"# optimization objective function\n",
"def mpp(vsys, nseries_cells, nmods, nstrings, nbypass,\n",
" il, i0, rsh, rs, gamma, vt, vbypass, guess):\n",
" r = fsolve(isys_from_voltage, x0=guess,\n",
" args=(nseries_cells, nmods, nstrings, nbypass,\n",
" il, i0, rsh, rs, gamma, vt, vbypass, vsys),\n",
" full_output=False)\n",
" psys = r[-1]*vsys\n",
" return -psys"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"290.3329375367371"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# estimate upper limit of system voltage\n",
"VOC_EST = (GAMMA*VT * np.log(IL / I0 + 1.0))*NMODS*NSERIES_CELLS\n",
"VOC_EST"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
" fun: -4625.825388207809\n",
" nfev: 16\n",
" nit: 12\n",
" success: True\n",
" x: 238.24038460561962"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# find max power\n",
"optimum = minimize_scalar(mpp, (0, VSYS, VOC_EST),\n",
" args=(NSERIES_CELLS, NMODS, NSTRINGS, NBYPASS,\n",
" IL+noise, I0, RSH, RS, GAMMA, VT, VBYPASS, X0))\n",
"optimum"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"238.24038460561962"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Vmp = optimum.x\n",
"Vmp"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"4625.825388207809"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Pmp = -optimum.fun\n",
"Pmp"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"19.41662995493122"
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Imp = Pmp / Vmp\n",
"Imp"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.5"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
@mikofski
Copy link
Author

Note: finding the max power point may be an non-convex optimization problem, therefore to guarantee convergence, find the index of
the max power point by calling numpy.argmax(power). If the spacing of points on the IV curve is too course, then use brentq in a convex trust region around the index of the max power point.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment