/// <summary> /// Solves the ADE from time ti to tf. Diffusion of solute ignored - dispersion /// coeff = dispersivity* abs(pore water velocity). /// </summary> /// <param name="ti">start time (h).</param> /// <param name="tf">finish time.</param> /// <param name="thi">initial layer water contents.</param> /// <param name="thf">initial layer water contents.</param> /// <param name="dwexs">water extracted from layers over period ti to tf.</param> /// <param name="win">water in at top of profile.</param> /// <param name="cin">solute concn in win.</param> /// <param name="n">no. of soil layers.</param> /// <param name="ns">no. of solutes.</param> /// <param name="nex">no. of water extraction streams.</param> /// <param name="dx">layer thicknesses.</param> /// <param name="jt">layer soil type numbers for solute.</param> /// <param name="dsmmax">max change in sm of any layer to aim for each time step; controls time step size.</param> /// <param name="sm">layer masses of solute per cc.</param> /// <param name="sdrn">cumulative solute drainage.</param> /// <param name="nssteps">cumulative no. of time steps for ADE soln.</param> /// <param name="c"></param> /// <param name="sex">cumulative solute extractions in water extraction streams.</param> /// <param name="extraction">bool indicating if solute extraction is enabled.</param> /// <param name="solProps">Solute properties.</param> private static void Solute(double ti, double tf, double[] thi, double[] thf, double[,] dwexs, double win, double[] cin, int n, int ns, int nex, double[] dx, int[] jt, double dsmmax, ref double[,] sm, ref double[] sdrn, ref int[] nssteps, ref double[,] c, ref double[,,] sex, bool extraction, SolProps solProps) { int itmax = 20; //max iterations for finding c from sm double eps = 0.00001; // for stopping int i, it, j, k; double dc, dm, dmax, dt = 0, f = 0, fc = 0, r, rsig, rsigdt, sig, sigdt, t, tfin, th, v1, v2; double[] dz = new double[n - 1 + 1]; double[] coef1 = new double[n - 1 + 1]; double[] coef2 = new double[n - 1 + 1]; double[] csm = new double[n + 1]; double[] tht = new double[n + 1]; double[] dwex = new double[n + 1]; double[] qsex = new double[n + 1]; double[] qsexd = new double[n + 1]; double[,] qsexs = new double[nex + 1, n + 1]; double[,] qsexsd = new double[nex + 1, n + 1]; double[] aa = new double[n + 1]; // these are 0 based double[] bb = new double[n + 1]; double[] cc = new double[n + 1]; double[] dd = new double[n + 1]; double[] dy = new double[n + 1]; double[] ee = new double[n + 1]; double[] q = new double[n + 1]; double[] qw = new double[n + 1]; double[] qya = new double[n + 1]; double[] qyb = new double[n + 1]; sig = 0.5; rsig = 1.0 / sig; tfin = tf; for (int x = 2; x <= n; x++) { dz[x - 1] = 0.5 * (dx[x - 1] + dx[x]); } //get average water fluxes //dwex = sum(dwexs, 2) !total changes in sink water extraction since last call if (dwexs.GetLength(0) > 0 && dwexs.GetLength(1) > 0) { Matrix <double> dwexsM = Matrix <double> .Build.DenseOfArray(dwexs); for (int x = 0; x < dwexs.GetLength(1); x++) { dwex[x] = MathUtilities.Sum(dwexsM.Row(x)); } } r = 1.0 / (tf - ti); qw[0] = r * win; // Compounding errors due to float/double issues start here. May or may not be a problem. tht = MathUtilities.Multiply_Value(MathUtilities.Subtract(thf, thi), r); for (i = 1; i <= n; i++) { qw[i] = qw[i - 1] - dx[i] * tht[i] - r * dwex[i]; } //get constant coefficients for (i = 1; i <= n - 1; i++) { v1 = 0.5 * qw[i]; v2 = 0.5 * (solProps.dis[jt[i]] + solProps.dis[jt[i + 1]]) * Math.Abs(qw[i]) / dz[i]; coef1[i] = v1 + v2; coef2[i] = v1 - v2; } for (j = 1; j <= ns; j++) { t = ti; if (qw[0] > 0.0) { q[0] = qw[0] * cin[j]; } else { q[0] = 0.0; } qyb[0] = 0.0; while (t < tfin) //get fluxes { for (i = 1; i <= n; i++) { //get c and csm = dc / dsm(with theta constant) k = jt[i]; th = thi[i] + (t - ti) * tht[i]; if (solProps.isotype[j, k] == "no" || sm[j, i] < 0.0) //handle sm < 0 here { csm[i] = 1.0 / th; c[j, i] = csm[i] * sm[j, i]; } else if (solProps.isotype[j, k] == "li") { csm[i] = 1.0 / (th + solProps.bd[k] * solProps.isopar[k, j].ElementAt(1)); c[j, i] = csm[i] * sm[j, i]; } else { for (it = 1; it <= itmax; it++) //get c from sm using Newton's method and bisection { if (c[j, i] < 0.0) { c[j, i] = 0.0; //c and sm are >= 0 } solProps.Isosub(solProps.isotype[j, k], c[j, i], dsmmax, ref solProps.isopar[j, k], out f, out fc); csm[i] = 1.0 / (th + solProps.bd[k] * fc); dm = sm[j, i] - (solProps.bd[k] * f + th * c[j, i]); dc = dm * csm[i]; if (sm[j, i] >= 0.0 && c[j, i] + dc < 0.0) { c[j, i] = 0.5 * c[j, i]; } else { c[j, i] = c[j, i] + dc; } if (Math.Abs(dm) < eps * (sm[j, i] + 10.0 * dsmmax)) { break; } if (it == itmax) { Console.WriteLine("solute: too many iterations getting c"); Environment.Exit(1); } } } } for (int x = 1; x <= n - 1; x++) { q[x] = coef1[x] * c[j, x] + coef2[x] * c[j, x + 1]; qya[x] = coef1[x] * csm[x]; qyb[x] = coef2[x] * csm[x + 1]; } q[n] = qw[n] * c[j, n]; qya[n] = qw[n] * csm[n]; //get time step double[] absQ = new double[n + 1]; for (int x = 1; x <= n; x++) { absQ[x] = Math.Abs(q[x] - q[x - 1] / dx[x]); } dmax = MathUtilities.Max(absQ); if (dmax == 0.0) { dt = tfin - t; } else if (dmax < 0.0) { Console.WriteLine("solute: errors in fluxes prevent continuation"); Environment.Exit(1); } else { dt = dsmmax / dmax; } if (t + 1.1 * dt > tfin) { dt = tfin - t; t = tfin; } else { t = t + dt; } sigdt = sig * dt; rsigdt = 1.0 / sigdt; //adjust q for change in theta for (int x = 1; x <= n - 1; x++) { q[x] = q[x] - sigdt * (qya[x] * tht[x] * c[j, x] + qyb[x] * tht[x + 1] * c[j, x]); } q[n] = q[n] - sigdt * qya[n] * tht[n] * c[j, n]; //get and solve eqns for (int x = 2; x <= n; x++) { aa[x] = qya[x - 1]; } for (int x = 1; x <= n - 1; x++) { cc[x] = -qyb[x]; } Matrix <double> qsexsM = Matrix <double> .Build.DenseOfArray(qsexs); Matrix <double> qsexsdM = Matrix <double> .Build.DenseOfArray(qsexsd); if (sex.GetLength(0) > 1) //get extraction { double[] ctemp = new double[n + 1]; for (int x = 1; x <= n; x++) { ctemp[x] = c[j, x]; } qsexs = qsexsM.ToArray(); qsexsd = qsexsdM.ToArray(); sink.Ssinks(t, ti, tf, j, dwexs, ctemp, ref qsexs, ref qsexsd); qsex = qsexsM.ColumnSums().ToArray(); qsexd = qsexsdM.ColumnSums().ToArray(); for (int x = 1; x <= n; x++) { bb[x] = qyb[x - 1] - qya[x] - qsexd[x] * csm[x] - dx[x] * rsigdt; dd[x] = -(q[x - 1] - q[x] - qsex[x]) * rsig; } } else { for (int x = 1; x <= n; x++) { bb[x] = qyb[x - 1] - qya[x] - dx[x] * rsigdt; dd[x] = -(q[x - 1] - q[x]) * rsig; } } Tri(1, n, aa, ref bb, cc, dd, ref ee, ref dy); //update unknowns Matrix <double> smM = Matrix <double> .Build.DenseOfArray(sm); qsexsM = Matrix <double> .Build.DenseOfArray(qsexs); qsexsdM = Matrix <double> .Build.DenseOfArray(qsexsd); sdrn[j] = sdrn[j] + (q[n] + sig * qya[n] * dy[n]) * dt; smM.SetRow(j, smM.Row(j) + Vector <double> .Build.DenseOfArray(dy.Slice(1, n))); sm = smM.ToArray(); if (sex.GetLength(0) > 1) // need to test, this will need to be transposed. { Matrix <double> sexM = Matrix <double> .Build.Dense(sex.GetLength(0), sex.GetLength(1)); for (int x = 0; x < sex.GetLength(0); x++) { for (int y = 0; y < sex.GetLength(1); y++) { sexM[x, y] = sex[x, y, j]; } } Vector <double> dysub = Vector <double> .Build.DenseOfArray(dy.Slice(1, n)); for (i = 1; i <= nex; i++) { sexM.SetRow(i, sexM.Row(i) + (qsexsM.Row(i) + sig * qsexsdM.Row(i) * Vector <double> .Build.DenseOfArray(csm) * dysub) * dt); } for (int x = 0; x < sex.GetLength(0); x++) { for (int y = 0; y < sex.GetLength(1); y++) { sex[j, y, x] = sexM[y, x]; } } } nssteps[j] = nssteps[j] + 1; } } }
/// <summary> /// Solves the ADE from time ti to tf. Diffusion of solute ignored - dispersion /// coeff = dispersivity* abs(pore water velocity). /// </summary> /// <param name="ti">start time (h).</param> /// <param name="tf">finish time.</param> /// <param name="thi">initial layer water contents.</param> /// <param name="thf">initial layer water contents.</param> /// <param name="dwexs">water extracted from layers over period ti to tf.</param> /// <param name="win">water in at top of profile.</param> /// <param name="cin">solute concn in win.</param> /// <param name="n">no. of soil layers.</param> /// <param name="ns">no. of solutes.</param> /// <param name="nex">no. of water extraction streams.</param> /// <param name="dx">layer thicknesses.</param> /// <param name="jt">layer soil type numbers for solute.</param> /// <param name="dsmmax">max change in sm of any layer to aim for each time step; controls time step size.</param> /// <param name="sm">layer masses of solute per cc.</param> /// <param name="sdrn">cumulative solute drainage.</param> /// <param name="nssteps">cumulative no. of time steps for ADE soln.</param> /// <param name="c"></param> /// <param name="sex">cumulative solute extractions in water extraction streams.</param> /// <param name="extraction">bool indicating if solute extraction is enabled.</param> /// <param name="solProps">Solute properties.</param> private static void Solute(double ti, double tf, double[] thi, double[] thf, double[,] dwexs, double win, double[] cin, int n, int ns, int nex, double[] dx, int[] jt, double dsmmax, ref double[,] sm, ref double[] sdrn, ref int[] nssteps, ref double[,] c, ref double[,,] sex, bool extraction, SolProps solProps) { int itmax = 20; //max iterations for finding c from sm double eps = 0.00001; // for stopping int i, it, j, k; double dc, dm, dmax, dt=0, f=0, fc=0, r, rsig, rsigdt, sig, sigdt, t, tfin, th, v1, v2; double[] dz = new double[n - 1 + 1]; double[] coef1 = new double[n - 1 + 1]; double[] coef2 = new double[n - 1 + 1]; double[] csm = new double[n + 1]; double[] tht = new double[n + 1]; double[] dwex = new double[n + 1]; double[] qsex = new double[n + 1]; double[] qsexd = new double[n + 1]; double[,] qsexs = new double[nex + 1, n + 1]; double[,] qsexsd = new double[nex + 1, n + 1]; double[] aa = new double[n + 1]; // these are 0 based double[] bb = new double[n + 1]; double[] cc = new double[n + 1]; double[] dd = new double[n + 1]; double[] dy = new double[n + 1]; double[] ee = new double[n + 1]; double[] q = new double[n + 1]; double[] qw = new double[n + 1]; double[] qya = new double[n + 1]; double[] qyb = new double[n + 1]; sig = 0.5; rsig = 1.0 / sig; tfin = tf; for (int x = 2; x <= n; x++) dz[x - 1] = 0.5 * (dx[x - 1] + dx[x]); //get average water fluxes //dwex = sum(dwexs, 2) !total changes in sink water extraction since last call if (dwexs.GetLength(0) > 0 && dwexs.GetLength(1) > 0) { Matrix<double> dwexsM = Matrix<double>.Build.DenseOfArray(dwexs); for (int x = 0; x < dwexs.GetLength(1); x++) { dwex[x] = MathUtilities.Sum(dwexsM.Row(x)); } } r = 1.0 / (tf - ti); qw[0] = r * win; // Compounding errors due to float/double issues start here. May or may not be a problem. tht = MathUtilities.Multiply_Value(MathUtilities.Subtract(thf, thi), r); for (i = 1; i <= n; i++) qw[i] = qw[i - 1] - dx[i] * tht[i] - r * dwex[i]; //get constant coefficients for (i = 1; i <= n - 1; i++) { v1 = 0.5 * qw[i]; v2 = 0.5 * (solProps.dis[jt[i]] + solProps.dis[jt[i + 1]]) * Math.Abs(qw[i]) / dz[i]; coef1[i] = v1 + v2; coef2[i] = v1 - v2; } for (j = 1; j <= ns; j++) { t = ti; if (qw[0] > 0.0) q[0] = qw[0] * cin[j]; else q[0] = 0.0; qyb[0] = 0.0; while (t < tfin) //get fluxes { for (i = 1; i <= n; i++) { //get c and csm = dc / dsm(with theta constant) k = jt[i]; th = thi[i] + (t - ti) * tht[i]; if (solProps.isotype[j, k] == "no" || sm[j, i] < 0.0) //handle sm < 0 here { csm[i] = 1.0 / th; c[j, i] = csm[i] * sm[j, i]; } else if (solProps.isotype[j, k] == "li") { csm[i] = 1.0 / (th + solProps.bd[k] * solProps.isopar[k, j].ElementAt(1)); c[j, i] = csm[i] * sm[j, i]; } else { for (it = 1; it <= itmax; it++) //get c from sm using Newton's method and bisection { if (c[j, i] < 0.0) c[j, i] = 0.0; //c and sm are >= 0 solProps.Isosub(solProps.isotype[j, k], c[j, i], dsmmax, ref solProps.isopar[j,k], out f, out fc); csm[i] = 1.0 / (th + solProps.bd[k] * fc); dm = sm[j, i] - (solProps.bd[k] * f + th * c[j, i]); dc = dm * csm[i]; if (sm[j, i] >= 0.0 && c[j, i] + dc < 0.0) c[j, i] = 0.5 * c[j, i]; else c[j, i] = c[j, i] + dc; if (Math.Abs(dm) < eps * (sm[j, i] + 10.0 * dsmmax)) break; if (it == itmax) { Console.WriteLine("solute: too many iterations getting c"); Environment.Exit(1); } } } } for (int x = 1; x <= n - 1; x++) { q[x] = coef1[x] * c[j, x] + coef2[x] * c[j, x + 1]; qya[x] = coef1[x] * csm[x]; qyb[x] = coef2[x] * csm[x + 1]; } q[n] = qw[n] * c[j, n]; qya[n] = qw[n] * csm[n]; //get time step double[] absQ = new double[n+1]; for (int x = 1; x <= n; x++) absQ[x] = Math.Abs(q[x] - q[x - 1] / dx[x]); dmax = MathUtilities.Max(absQ); if (dmax == 0.0) dt = tfin - t; else if (dmax < 0.0) { Console.WriteLine("solute: errors in fluxes prevent continuation"); Environment.Exit(1); } else dt = dsmmax / dmax; if (t + 1.1 * dt > tfin) { dt = tfin - t; t = tfin; } else t = t + dt; sigdt = sig * dt; rsigdt = 1.0 / sigdt; //adjust q for change in theta for (int x = 1; x <= n - 1; x++) q[x] = q[x] - sigdt * (qya[x] * tht[x] * c[j, x] + qyb[x] * tht[x + 1] * c[j, x]); q[n] = q[n] - sigdt * qya[n] * tht[n] * c[j, n]; //get and solve eqns for (int x = 2; x <= n; x++) aa[x] = qya[x - 1]; for (int x = 1; x <= n - 1; x++) cc[x] = -qyb[x]; Matrix<double> qsexsM = Matrix<double>.Build.DenseOfArray(qsexs); Matrix<double> qsexsdM = Matrix<double>.Build.DenseOfArray(qsexsd); if (sex.GetLength(0) > 1) //get extraction { double[] ctemp = new double[n + 1]; for (int x = 1; x <= n; x++) ctemp[x] = c[j, x]; qsexs = qsexsM.ToArray(); qsexsd = qsexsdM.ToArray(); sink.Ssinks(t, ti, tf, j, dwexs, ctemp, ref qsexs, ref qsexsd); qsex = qsexsM.ColumnSums().ToArray(); qsexd = qsexsdM.ColumnSums().ToArray(); for (int x = 1; x <= n; x++) { bb[x] = qyb[x - 1] - qya[x] - qsexd[x] * csm[x] - dx[x] * rsigdt; dd[x] = -(q[x - 1] - q[x] - qsex[x]) * rsig; } } else { for (int x = 1; x <= n; x++) { bb[x] = qyb[x - 1] - qya[x] - dx[x] * rsigdt; dd[x] = -(q[x - 1] - q[x]) * rsig; } } Tri(1, n, aa, ref bb, cc, dd, ref ee, ref dy); //update unknowns Matrix<double> smM = Matrix<double>.Build.DenseOfArray(sm); qsexsM = Matrix<double>.Build.DenseOfArray(qsexs); qsexsdM = Matrix<double>.Build.DenseOfArray(qsexsd); sdrn[j] = sdrn[j] + (q[n] + sig * qya[n] * dy[n]) * dt; smM.SetRow(j, smM.Row(j) + Vector<double>.Build.DenseOfArray(dy.Slice(1, n))); sm = smM.ToArray(); if (sex.GetLength(0) > 1) // need to test, this will need to be transposed. { Matrix<double> sexM = Matrix<double>.Build.Dense(sex.GetLength(0), sex.GetLength(1)); for (int x = 0; x < sex.GetLength(0); x++) for (int y = 0; y < sex.GetLength(1); y++) sexM[x, y] = sex[x, y, j]; Vector<double> dysub = Vector<double>.Build.DenseOfArray(dy.Slice(1, n)); for (i = 1; i <= nex; i++) sexM.SetRow(i, sexM.Row(i) + (qsexsM.Row(i) + sig * qsexsdM.Row(i) * Vector<double>.Build.DenseOfArray(csm) * dysub) * dt); for (int x = 0; x < sex.GetLength(0); x++) for (int y = 0; y < sex.GetLength(1); y++) sex[j, y, x] = sexM[y, x]; } nssteps[j] = nssteps[j] + 1; } } }
public void Isosub() { SolProps sp = new SolProps(2, 10); string iso = "Fr"; double c = 0; double dsmmax = 10; double[] p = { 0, 1, 0.5, 0, 0 }; double[] pOut = { 0, 1, 0.5, 0.01, 10 }; double f; double fd; double fOut = 0; double fdOut = 10; sp.Isosub(iso, c, dsmmax, ref p, out f, out fd); for (int i = 0; i < p.Length; i++) Assert.AreEqual(pOut[i], p[i], Math.Abs(pOut[i] * 1E-5)); Assert.AreEqual(fOut, f, Math.Abs(fOut * 1E-5)); Assert.AreEqual(fdOut, fd, Math.Abs(fdOut * 1E-5)); }