# Steam reforming with water-gas shift

## Known coversion and temperature in steam reforming

Given temperature and conversion of steam reforming (DR), assume exit composition is in equilibrium with respect to water-gas shift (WGS), and determine WGS-conversion $X_{H_2O,WGS}$.

Parameters:
* $X_{CH_4}=0.2$
* $T_{DR}=624~^\circ C$
* $S/C=2.2$

```python
from numpy import array, log, exp, sqrt, zeros, prod, linspace
from scipy.optimize import bisect
from matplotlib import pyplot as plt

data=[line.split(';') for line in """z_i/(-);h_ig/(J/mol);s_ig/(J/mol/K);g_ig/(J/mol);cp_ig/(J/mol/K);cas_no/();phase/();formula/();formula_name_structure/();ant_name/();poling_no/();poling_formula/();poling_name/();poling_molwt/(g/mol);poling_tfp/(K);poling_tb/(K);poling_tc/(K);poling_pc/(bar);poling_vc/(cm3/mol);poling_zc/();poling_omega/();poling_delhf0/(kJ/mol);poling_delgf0/(kJ/mol);poling_delhb/(kJ/mol);poling_delhm/(kJ/mol);poling_v_liq/(cm3/mol);poling_t_liq/(K);poling_dipole/(Debye);p_ant_a/();p_ant_b/(K);p_ant_c/(K);p_ant_tmin/(K);p_ant_tmax/(K);p_ant_pvpmin/(bar);p_ant_pvpmax/(bar);eant_to/(K);eant_n/();eant_e/();eant_f/();eant_tmin/(K);eant_tmax/(K);eant_pvpmin/(bar);eant_pvpmax/(bar);wagn_a/();wagn_b/();wagn_c/();wagn_d/();wagn_tmin/(K);wagn_tmax/(K);wagn_pvpmin/(bar);wagn_pvpmax/(bar);range_tmin_to_1000/(K);range_1000_to_tmax/(K);molecular_weight/(g/mol);hf298_div_r/();a1_low/();a2_low/(K^-1);a3_low/(K^-2);a4_low/(K^-3);a5_low/(K^-4);a6_low/(K^-1);a7_low/();a1_high/();a2_high/(K^-1);a3_high/(K^-2);a4_high/(K^-3);a5_high/(K^-4);a6_high/(K^-1);a7_high/();reference/();source/();date/();ant_no/();ant_formula/();ant_name/();ant_a/();ant_b/();ant_c/();ant_tmin/(°C);ant_tmax/(°C);ant_code/()
0;0;130,802012845;-38998,6201299;28,8363121833;1333-74-0;G;H2 REF ELEMENT;H2 CALC FROM GURVIC'S TABLE;hydrogen;438;H2;hydrogen;2,016;13,83;20,27;32,98;12,93;64,2;0,303;-0,217;0;0;0,89;0,12;28,39;20;0;2,93954;66,7954;275,65;10,25;22,82;0,05;2;;;;;;;;;;;;;;;;;200;6000;2,01588;0;2,34433112;0,00798052075;-1,9478151e-05;2,01572094e-08;-7,37611761e-12;-917,935173;0,683010238;2,93286575;0,000826608026;-1,46402364e-07;1,54100414e-11;-6,888048e-16;-813,065581;-1,02432865;GURVICH 78;tpis;78;161;H2;hydrogen;6,14858;80,948;277,53;-259,2;-239,97;1,2;
0;-74520;186,034253085;-129986,112557;35,6126668172;74-82-8;G;CH4   RRHO;CH4 METHANE SAME AS THE ANHARMONIC BUT CALCULATED USING THE RRHO METHOD RATHER THAN THE NRRAO2.;methane;26;CH4;methane;16,043;90,69;111,66;190,56;45,992;98,6;0,286;0,011;-74,52;-50,45;8,17;0,94;35,54;90,68;0;3,7687;395,744;266,681;92,64;120,59;0,15;2;;;;;;;;;-6,02242;1,26652;-0,5707;-1,366;;190,55;;45,99;200;6000;16,04246;-8972,26656;5,14825732;-0,013700241;4,93749414e-05;-4,91952339e-08;1,70097299e-11;-10245,3222;-4,63322726;1,911786;0,0096026796;-3,38387841e-06;5,3879724e-10;-3,19306807e-14;-10099,2136;8,48241861;;g;8/99;137;CH4;methane;6,84377;435,4534;271,361;-196,85;-82,59;1,2;
0;-393510;213,789479981;-457251,333456;37,1352585732;124-38-9;G;CO2;CO2 CARBON-DIOXIDE;carbon dioxide;31;CO2;carbon dioxide;44,01;216,58;;304,12;73,74;94,07;0,274;0,225;-393,51;-394,38;;9,02;;;0;;;;;;;;;;;;;;;;;;;;;;;;200;6000;44,0098;-47328,105;2,356813;0,0089841299;-7,1220632e-06;2,4573008e-09;-1,4288548e-13;-48371,971;9,9009035;4,6365111;0,0027414569;-9,9589759e-07;1,6038666e-10;-9,1619857e-15;-49024,904;-1,9348955;GURVICH VOL 2 1991 P.27;L;7/88;164;CO2;carbon dioxide;9,81367;1340,9768;271,883;-119,74;-8,96;1,2;
0;-110530;197,672063199;-169465,925643;29,1409651441;630-08-0;G;CO;CO CARBON-MONOXIDE CALCULATED FROM TSIV TABLE.;carbon monoxide;30;CO;carbon monoxide;28,01;68,15;81,66;132,85;34,94;93,1;0,292;0,045;-110,53;-137,16;6,04;0,84;34,88;81;0,1;3,81912;291,743;267,996;69,73;88,08;0,2;2;;;;;;;;;;;;;;;;;200;6000;28,0104;-13293,628;3,5795335;-0,00061035369;1,0168143e-06;9,0700586e-10;-9,0442449e-13;-14344,086;3,5084093;3,0484859;0,0013517281;-4,8579405e-07;7,8853644e-11;-4,6980746e-15;-14266,117;6,0170977;TSIV 79;RUS;79;161;CO;carbon monoxide;6,72828;295,2279;268,243;-216,7;-140,29;1,2;
0;-241810;188,799783187;-298100,655357;33,58766993;7732-18-5;G;H2O;H2O;water;440;H2O;water;18,015;273,15;373,15;647,14;220,64;55,95;0,229;0,344;-241,81;-228,42;40,66;6,01;18,07;298,15;1,8;5,11564;1687,537;230,17;273,2;473,2;0,01;16;;;;;;;;;-7,77224;1,45684;-2,71942;-1,41336;273,2;647,3;0,01;221;200;6000;18,01528;-29084,817;4,1986352;-0,0020364017;6,5203416e-06;-5,4879269e-09;1,771968e-12;-30293,726;-0,84900901;2,6770389;0,0029731816;-7,7376889e-07;9,4433514e-11;-4,2689991e-15;-29885,894;6,88255;WOOLEY J. RES. NBS 92 (1987), 35.  BASED ON HF298(L) FROM COX, WAGMAN & MEDVEDEV CODATA KEY VAL. FOR THERMO, HEMISPHERE 1989 P.21 AND HEAT OF VAP. FROM HAAR, GALLAGHER & KELL NBS/NRC TABLES, HEMISPHERE 1984.;L;5/89;162;H2O;water;8,05573;1723,6425;233,08;0,01;373,98;1,2;
0;0;191,632621307;-57135,2660428;29,124315278;7727-37-9;G;N2  REF ELEMENT;N2 REFERENCE ELEMENT HF=0. FROM TSIV TABLES;nitrogen;455;N2;nitrogen;28,014;63,15;77,35;126,2;34;90,1;0,289;0,037;0;0;5,58;0,72;34,84;78;0;3,61947;255,68;266,55;60,81;83,65;0,08;2;;;;;;;;;-6,11102;1,2189;-0,69366;-1,89893;;126,2;;34;200;6000;28,0134;0;3,53100528;-0,000123660988;-5,02999433e-07;2,43530612e-09;-1,40881235e-12;-1046,97628;2,96747038;2,95257637;0,0013969004;-4,92631603e-07;7,86010195e-11;-4,60755204e-15;-923,948688;5,87188762;;G;8/02;239;N2;nitrogen;6,72531;285,5727;270,09;-210;-147,05;1,2;
0;0;205,095940942;-61149,3547919;29,3783179536;7782-44-7;G;O2 REF ELEMENT;O2 CALCULATED FROM ORIGINAL VALUES;oxygen;460;O2;oxygen;31,999;54,36;90,17;154,58;50,43;73,37;0,288;;0;0;6,82;0,44;27,85;90;0;3,81634;319,013;266,7;64,29;97,2;0,02;2;;;;;;;;;;;;;;;;;200;6000;31,9988;0;3,78245636;-0,00299673416;9,84730201e-06;-9,68129509e-09;3,24372837e-12;-1063,94356;3,65767573;3,66096065;0,000656365811;-1,41149627e-07;2,05797935e-11;-1,29913436e-15;-1215,97718;3,41536279;GURVICH 1989. CORRECTED BY B.MCBRIDE NASA TP-2002-211556;TPIS;89;271;O2;oxygen;6,83706;339,2095;268,7;-218,8;-118,57;1,2;
0;0;154,846476768;-46167,4770484;20,78625;7440-37-1;G;AR REF ELEMENT;AR;argon;1;Ar;argon;39,948;83,8;87,27;150,86;48,98;74,57;0,291;-0,002;0;0;6,43;;29,1;90;0;3,74141;304,227;267,32;82,59;94,26;0,6;2;;;;;;;;;;;;;;;;;200;6000;39,948;0;2,5;0;0;0;0;-745,375;4,37967491;2,5;0;0;0;0;-745,375;4,37967491;C.E. MOORE ATOMIC ENERGY LEVELS NSRDS-NBS 35 (1971) P.211;g;5/97;15;Ar;argon;6,84064;340,2707;271,8;-189,37;-122,29;1,2;
0;0;126,153545211;-37612,6795046;20,78625;7440-59-7;G;He REF ELEMENT;HE;helium-4;450;He;helium;4,003;2,15;4,3;5,19;2,27;57,3;0,301;-0,39;0;0;0,08;;32,54;4,3;0;1,6836;8,1548;273,71;1,85;5,34;0,02;2;;;;;;;;;;;;;;;;;200;6000;4,0026;0;2,5;0;0;0;0;-745,375;0,928723974;2,5;0;0;0;0;-745,375;0,928723974;;g;5/97;175;He;helium-4;5,2712;13,5171;274,58;-271,39;-267,95;1,2;
0;82880;268,325246538;2878,82774459;81,934398014;71-43-2;G;C6H6;[F&W NOTATION A1] C6H6 BENZENE;benzene;187;C6H6;benzene;78,114;278,68;353,24;562,05;48,98;256;0,268;0,21;82,88;129,75;30,72;9,95;89,41;298,15;0;3,98523;1184,24;217,572;279,64;377,06;0,05;2;;;;;;;;;-7,01433;1,55256;-1,8479;-3,713;;562,16;;48,98;200;6000;78,11184;9968,11598;0,504818632;0,0185020642;7,38345881e-05;-1,18135741e-07;5,07210429e-11;8552,47913;21,6412893;11,0809576;0,0207176746;-7,52145991e-06;1,22320984e-09;-7,36091279e-14;4306,41035;-40,041331;SHIMANOUCHI AND PLIVA ET AL J. MOLEC. SPETROS 107,(1984),209;g;6/01;2460;C6H6;benzene;6,81432;1090,4312;197,146;-9,6;103,04;1,2;
0;-123100;295,971523164;-211343,909631;105,342922613;110-82-7;G;C6H12,cyclo-;C6H12 CYCLOHEXANE;cyclohexane;197;C6H!2;cyclohexane;84,161;279,69;353,93;553,5;40,73;308;0,273;0,211;-123,1;32,26;29,97;2,63;108,75;298,15;0,3;3,93002;1182,774;220,618;282,11;378,46;0,06;2;25;3,40407;10,048;-126,96;378,15;553,15;1,9871;40,48;;;;;;;;;200;6000;84,15948;-14829,4969;4,04357527;-0,00619608335;0,000176622274;-2,22968474e-07;8,63668578e-11;-16920,3544;8,52527441;13,214597;0,0358243434;-1,32110852e-05;2,17202521e-09;-1,31730622e-13;-22809,2102;-55,3518322;DOROFEEVA GURVICH & JORISH JPCRD 15 (1986) 437;g;6/90;2823;C6H12;cyclohexane;6,88938;1200,8256;218,815;-14,92;105,07;1,2;
0;-166920;387,462696222;-282442,002879;142,590479962;110-54-3;G;C6H14,n-hexane;C6H14 N-HEXANE;hexane;216;c6hm;hexane;86,177;177,84;341,88;507,6;30,35;368;0,264;0,3;-166,92;0,15;28,85;13,07;131,59;298,15;0;4,00139;1170,875;224,317;254,24;365,25;0,02;2;;;;;;;;;-7,53998;1,83759;-2,5438;-3,163;;507,9;;30,35;200;6000;86,17536;-20075,7471;9,87121167;-0,00936699002;0,000169887865;-2,1501952e-07;8,45407091e-11;-23718,5495;-12,4999353;19,5158086;0,0267753942;-7,49783741e-06;1,19510646e-09;-7,51957473e-14;-29436,2466;-77,4895497;;g;6/01;3139;C6H14;hexane;6,98978;1216,9154;227,451;-24,29;92,12;1,2;
0;-200940;240,19493704;-272554,120478;42,9167106558;67-56-1;G;CH3OH Methyl alc;CH4O METHANOL (CH3OH);methyl alcohol;27;ch4o;methanol;32,042;175,49;337,69;512,64;80,92;118;0,224;0,565;-200,94;-162,24;35,21;3,18;40,73;298,15;1,7;5,20277;1580,08;239,5;262,59;356;0,02;2;;;;;;;;;-8,63571;1,17982;-2,479;-1,024;;512,64;;80,92;200;6000;32,04216;-24174,6056;5,65851051;-0,0162983419;6,91938156e-05;-7,58372926e-08;2,8042755e-11;-25611,9736;-0,897330508;3,52726795;0,0103178783;-3,62892944e-06;5,77448016e-10;-3,42182632e-14;-26002,8834;5,16758693;CHEN WILHOIT & ZWOLINSKI JPCRD 6,(1977),105;T;06/02;144;CH4O;methyl alcohol;8,08404;1580,4585;239,096;-15,99;199,45;1,2;
0;-352400;285,738660054;-437592,981495;62,4363045378;107-31-3;G;C2H4O2 methylfor;C2H4O2 METHYLFORMATE HCOOCH3;methyl formate;61;C2H4O2;methyl methanoate (methyl formate);60,053;174,15;304,9;487,2;60;172;0,255;;-352,4;-294,9;27,92;;62,14;298,15;1,8;4,29529;1125,2;230,56;230,3;324,29;0,02;2;;;;;;;;;;;;;;;;;200;6000;60,05196;-43032,7223;5,96757028;-0,00938085425;7,07648417e-05;-8,29932227e-08;3,13522917e-11;-44870,9982;0,750341113;6,3336088;0,0134851485;-4,84305805e-06;7,81719241e-10;-4,67917447e-14;-46131,3237;-6,91542601;ATCT C 2011;T;7/11;460;C2H4O2;methyl formate;7,05336;1138,3109;236,959;-48,91;214,05;1,2;
0;-378600;246,867144773;-452203,439214;41,3052664729;64-18-6;G;HCOOH FORMIC ACID;CH2O2 METHANOIC (FORMIC) ACID HCOOH MONOMER;formic acid;22;ch2o2;methanoic acid (formic acid);46,026;281,5;374,04;588;58,07;;;0,316;-378,6;-35,06;22,69;12,72;;;1,5;;;;;;;;;;;;;;;;-7,24917;0,44255;-0,35558;-0,96906;;588;;58,07;200;6000;46,02568;-45531,246;3,8983616;-0,0035587795;3,5520538e-05;-4,3849959e-08;1,7107769e-11;-46770,609;7,3495397;4,6138316;0,0064496364;-2,2908251e-06;3,6716047e-10;-2,1873675e-14;-47514,85;0,84788383;CHAO & ZWOLINSKI JPCRD 7.(1978),363;L;8/88;108;CH2O2;formic acid;6,96405;1162,8529;184,037;8,4;314,85;1,2;
0;-83820;228,779508382;-152030,610424;52,5009107486;74-84-0;G;C2H6;C2H6 ETHANE;ethane;65;C2H6;ethane;30,07;90,35;184,55;305,32;48,71;145,5;0,279;0,099;-83,82;-31,86;14,7;2,86;46,15;90,36;0;3,95405;663,72;256,681;133,8;198,16;0,02;2;;;;;;;;;-6,475;1,41071;-1,144;-1,859;;305,33;;48,71;200;6000;30,06904;-10084,9652;4,29142572;-0,00550154901;5,99438458e-05;-7,08466469e-08;2,68685836e-11;-11522,2056;2,66678994;4,04666411;0,0153538802;-5,47039485e-06;8,77826544e-10;-5,23167531e-14;-12447,3499;-0,968698313;CHAO WILHOIT & ZWOLINSKI JPCRD 2,(1973), 427;g;8/88;491;C2H6;ethane;6,95185;698,9298;260,264;-142,83;32,17;1,2;
0;52500;218,878873835;-12758,7362338;42,8868953326;74-85-1;G;C2H4;C2H4 ETHYLENE;ethylene;55;C2H4;ethene (ethylene);28,054;103,99;169,42;282,34;50,41;131,1;0,282;0,087;52,5;68,48;13,53;3,35;51,07;183,15;0;3,91382;596,526;256,37;123,06;181,9;0,02;2;-99;2,79132;9,717;52,77;188,15;273,15;2,71;40,99;;;;;;;;;200;6000;28,05316;6314,26266;3,95920063;-0,00757051373;5,70989993e-05;-6,91588352e-08;2,6988419e-11;5089,77598;4,09730213;3,99182724;0,0104833908;-3,71721342e-06;5,94628366e-10;-3,53630386e-14;4268,65851;-0,269081762;CHAO & ZWOLINSKY, JPCRD 4,(1975),251;g;1/00;428;C2H4;ethylene;6,96867;649,8057;262,73;-169,15;9,2;1,2;
0;-104680;269,715634825;-185095,716523;73,5887322571;74-98-6;G;C3H8;C3H8 PROPANE CH3CH2CH3;propane;95;C3H8;propane;44,097;91,45;231,02;369,83;42,47;200;0,276;0,152;-104,68;-24,29;19,04;3,53;74,87;233,15;0;3,92828;803,997;247,04;168,9;247,76;0,02;2;;;;;;;;;-6,76368;1,55481;-1,5872;-2,024;;369,85;;42,47;200;6000;44,09562;-12590,0384;4,21093013;0,00170886504;7,06530164e-05;-9,20060565e-08;3,64618453e-11;-14381,0883;5,61004451;6,6691976;0,0206108751;-7,36512349e-06;1,18434262e-09;-7,0691463e-14;-16275,4066;-13,1943379;CHAO WILHOIT & ZWOLINSKI JPCRD 2, (1973),427;g;2/00;959;C3H8;propane;7,02022;889,8642;257,084;-109,27;96,74;1,2;
0;20000;266,234106556;-59377,6988696;64,4333605623;115-07-1;G;C3H6 propylene;C3H6 PROPYLENE;propylene;85;C3H6;propene (propylene);42,081;87,89;225,46;364,9;46;184,6;0,28;0,142;20;62,5;18,42;3;;;0,4;3,95606;789,624;247,58;165,2;241,61;0,02;2;-41;2,67417;22,13;-199,34;238,15;363,15;1,74;44,67;;;;;;;;;200;6000;42,07974;2405,43339;3,83464468;0,00329078952;5,05228001e-05;-6,66251176e-08;2,63707473e-11;788,717123;7,53408013;6,03870234;0,0162963931;-5,821308e-06;9,35936829e-10;-5,58603143e-14;-741,715057;-8,43825992;CHAO & ZWOLINSKI JPCRD 4,(1975) 251;g;2/00;883;C3H6;propylene;7,00725;859,722;255,895;-112,78;91,06;1,2;
0;-20630;205,781166808;-81983,6548839;34,2548560182;7783-06-4;G;H2S anharmonic;H2S ANHARMONIC;hydrogen sulfide;441;H2S;hydrogen sulfide;34,082;187,62;212,84;373,4;89,63;98;0,283;0,09;-20,63;-33,43;18,68;2,38;34,32;214;0,9;4,22882;806,933;251,39;185,51;227,2;0,2;2;;;;;;;;;;;;;;;;;200;6000;34,08188;-2477,59639;4,12024455;-0,00187907426;8,2142665e-06;-7,0642573e-09;2,1423486e-12;-3682,15173;1,53174068;2,9787943;0,00359760372;-1,22803151e-06;1,96833209e-10;-1,16716162e-14;-3516,07638;6,77921228;GURVICH 1989;g;4/01;164;H2S;hydrogen sulfide;7,11958;802,2266;249,61;-85,47;100,38;1,2;
0;nan;311,477408685;nan;90,2348916052;7664-93-9;G;H2SO4;H2SO4 SULFURIC ACID;sulfuric acid;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;200;6000;98,07948;-88123,0524;4,53388173;0,0310347679;-4,10421795e-05;2,95752341e-08;-8,81459071e-12;-90545,9072;3,93961412;11,3355392;0,00560829109;-1,94574192e-06;3,07136054e-10;-1,81109544e-14;-92108,7435;-29,6094003;DOROFEEVA ET AL JPCRD 32 (2003),879 . CALCULATED FROM ORIGINAL TABLES.;T;8/03;165;H2SO4;sulfuric acid;8,28538;2976,837;213,95;25;336,85;1,2;
0;-395720;256,536422994;-472206,334516;50,6194285115;7446-11-9;G;SO3;SO3;sulfur trioxide;463;O3S;sulfur trioxide;80,064;289,95;317,9;490,9;82,1;126,5;0,254;;-395,72;-370,93;40,69;7,53;42,1;298,15;;6,17575;1735,31;236,5;284,5;332,04;0,15;2;;;;;;;;;;;;;;;;;200;6000;80,0642;-47615,554;2,37461122;0,0159543297;-1,26322543e-05;2,81827264e-09;6,23371547e-13;-48926,9231;13,1043046;7,29677572;0,00273576437;-1,06377755e-06;1,80776031e-10;-1,12077527e-14;-50309,6739;-12,4246659;GURVICH 89;tpis;89;361;SO3;sulfur trioxide;6,85638;754,8178;145,11;16,8;217,7;1,2;
0;-296810;248,127032091;-370789,074618;39,8424312772;7446-09-5;G;SO2;SO2 O-S-O;sulfur dioxide;461;O2S;sulfur dioxide;64,065;197,67;263,13;430,8;78,84;122;0,269;;-296,81;-300,14;24,94;7,4;44,03;263,15;1,6;4,4072;999,9;237,19;199,71;279,47;0,02;2;;;;;;;;;;;;;;;;;200;6000;64,0648;-35697,8343;3,67480752;0,00228302107;8,46893049e-06;-1,36562039e-08;5,76271873e-12;-36945,5073;7,9686643;5,38423482;0,0016793056;-6,32062944e-07;1,08465348e-10;-6,66890336e-15;-37606,7022;-1,83130517;GURVICH 89;tpis;89;356;SO2;sulfur dioxide;7,33311;1013,4609;237,65;-75,48;157,6;1,2;
T=298,15 K;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
""".split('\n')]
#df=pd.DataFrame(data)
#df.rename(columns=df.iloc[0])
#new_header=df.iloc[0]
#df=df[1:]
#df.columns=new_header

names=[item[7] for item in data[1:-2] if len(item[21])>0]  # exclude sulfuric acid with no entry on col. 21
delhf0=array([item[21].replace(',','.') for item in data[1:-2] if len(item[21])>0],dtype=float).T*1000  # exclude sulfuric acid with no entry on col. 21
delhg0=array([item[22].replace(',','.') for item in data[1:-2] if len(item[21])>0],dtype=float).T*1000  # exclude sulfuric acid with no entry on col. 21
a_low=array([[x.replace(',','.') for x in item[55:62]] for item in data[1:-2] if len(item[21])>0],dtype=float).T  # exclude sulfuric acid with no entry on col. 21
a_high=array([[x.replace(',','.') for x in item[62:69]] for item in data[1:-2] if len(item[21])>0],dtype=float).T  # exclude sulfuric acid with no entry on col. 21

R=8.3145 # J/mol/K

def therm_ig(t=298.15):
    """Returns thermodynamic properties of ideal gas at temperature t in K, based on NASA polynomials.
    Input: temperature in K.
    Output: h_ig/(J/mol), s_ig/(J/mol/K), g_ig/(J/mol), cp/(J/mol/K)
    """
    if t>1000: # poly a_low is for 200 - 1000 K; a_high is for 1000 - 6000 K
        a=a_high
    else:
        a=a_low

    cp_r=[sum([a[j][i]*t**j for j in range(4+1)]) for i in range(len(names))] # cp/R

    cp_ig=[R*cp_r[i] for i in range(len(names))] # J/mol/K
    s_cp_r_dt=[
    sum([1/(j+1)*a[j][i]*t**(j+1) for j in range(4+1)])
    -sum([1/(j+1)*a_low[j][i]*298.15**(j+1) for j in range(4+1)])
    for i in range(len(names))] # int(Cp/R*dT,298,15K,T)
    # int(Cp/R/T*dT,298.15K,T)
    s_cp_r_t_dt=[a[0][i]*log(t)+a[6][i]+
    sum([1/(j)*a[j][i]*t**(j) for j in range(1,3+1)])
    for i in range(len(names))] # int(Cp/(RT)*dT,0,T)

    h_ig=[delhf0[i]+R*s_cp_r_dt[i] for i in range(len(names))]
    s_ig=[R*s_cp_r_t_dt[i] for i in range(len(names))]
    g_ig=[h_ig[i]-t*s_ig[i] for i in range(len(names))]

    return h_ig,s_ig,g_ig,cp_ig

therm_ig(1000)
```

    ([20686.626894893176,
      -36385.27598061155,
      -360112.8226573553,
      -88839.93960027723,
      -215807.36501521352,
      21464.68067196319,
      22706.912993333346,
      14588.829562500001,
      14588.829562500001,
      195641.0081010494,
      37038.003752955585,
      10407.966001303255,
      -152833.92798258207,
      -281316.8411186896,
      -335540.43022776,
      -19409.66565972384,
      103138.895685806,
      -12254.578656265556,
      97508.82157894841,
      7445.125720528566,
      -348669.0926679975,
      -262470.16195549676],
     [181.5686500191445,
      212.1735085501309,
      269.5842720725006,
      236.42563008638112,
      229.05279803848896,
      231.1048428669693,
      236.84499369775233,
      180.00113520693745,
      151.30820364956546,
      342.30156937679214,
      367.9127322395452,
      495.47559182285755,
      259.49341403767585,
      335.15762180194724,
      281.5564421205502,
      275.7564914769493,
      244.21791761137004,
      341.4333385076708,
      335.62879576391714,
      248.32657968432588,
      333.2881006827243,
      293.65777758757054],
     [-160882.02312425134,
      -248558.78453074244,
      -629697.0947298559,
      -325265.56968665833,
      -444860.16305370245,
      -209640.1621950061,
      -214138.080704419,
      -165412.30564443744,
      -136719.37408706546,
      -146660.56127574272,
      -330874.7284865896,
      -485067.62582155433,
      -412327.34202025796,
      -616474.4629206369,
      -617096.8723483102,
      -295166.15713667317,
      -141079.02192556404,
      -353687.91716393636,
      -238119.97418496874,
      -240881.4539637973,
      -681957.1933507218,
      -556127.9395430672],
     [30.163282071570016,
      71.81610895494,
      54.32108144379001,
      33.16301082846,
      41.294930394900014,
      32.6829577808205,
      34.883131300604994,
      20.786250000000003,
      20.786250000000003,
      211.4109027116641,
      314.8547308469402,
      331.8589312233751,
      89.45941883884501,
      130.62600594658497,
      75.81102981644993,
      122.6859285015451,
      94.09744141500008,
      174.842297776365,
      144.6212822280001,
      46.00851930430501,
      75.98065149480148,
      54.320905924695])

(H2O+CH4, S/C=2.2) -->> DR (X_CH4=0,2) -->> WGS (GGW) -->> Prod

        (0)               (1)                 (2)

$\begin{align}y_{0,CH_4}=\frac{1}{1+2,2}=0,3125\\
y_{0,H_2O}=\frac{2,2}{1+2,2}=0,6875\end{align}$

DR, $X_{CH_4}$ fixed

$\tilde{y}_{i,DR}=\frac{\tilde{y}_{i,0}+\tilde{y}_{CH_4,0}\frac{\nu_{i,DR}}{-\nu_{CH_4,DR}}X_{CH_4}}{1+\underbrace{\tilde{y}_{CH_4,0}\frac{\sum_i(\nu_{i,DR})}{-\nu_{CH_4,DR}}}_{\epsilon_{CH_4}}X_{CH_4}}$

WGS, Equilibrium at $T_{DR}$ fixed

$K(T)=exp\left(\frac{-\Delta_R G^\circ(T)}{R T}\right)=K_\phi \frac{(\tilde{y}_{CO_2}p/p^\circ)(\tilde{y}_{H_2}p/p^\circ)}{(\tilde{y}_{CO}p/p^\circ)(\tilde{y}_{H_2O}p/p^\circ)}=1\cdot\frac{(\tilde{y}_{CO_2,DR}+\tilde{y}_{H_2O,DR}X_{H_2O,WGS})(\tilde{y}_{H_2,DR}+\tilde{y}_{H_2O,DR}X_{H_2O,WGS})}{(\tilde{y}_{CO,DR}-\tilde{y}_{H_2O,DR}X_{H_2O,WGS})(\tilde{y}_{H_2O,DR}-\tilde{y}_{H_2O,DR}X_{H_2O,WGS})}$

$\Rightarrow 0=(1-K(T))\cdot X_{H_2O,WGS}^2+\left(\frac{\tilde{y}_{CO_2,DR}}{\tilde{y}_{H_2O,DR}}+\frac{\tilde{y}_{H_2,DR}}{\tilde{y}_{H_2O,DR}}+K(T)\cdot\left(\frac{\tilde{y}_{CO,DR}}{\tilde{y}_{H_2O,DR}}+1\right)\right)\cdot X_{H_2O,WGS}+\frac{\tilde{y}_{CO_2,DR}\cdot\tilde{y}_{H_2,DR}}{\tilde{y}_{H_2O,DR}^2}-K(T)\frac{\tilde{y}_{CO,DR}}{\tilde{y}_{H_2O,DR}}$

(solve quadratic for $X_{H_2O,WGS} \Rightarrow \tilde{y}_{i,WGS}$)

```python
sc=2.2 # steam to carbon
X_ch4=0.2 # CH4 conversion
T=(624+273.15) # temperature in DR

nu=zeros([len(names),2]) # stoich. coefficients
nu[:5,0]=[3,-1,0,1,-1] # H2O+CH4->CO+3H2
nu[:5,1]=[1,0,1,-1,-1] # CO+H2O->CO2+H2

h,s,g,cp=therm_ig(T) # ideal gas therm. properties
delta_r_g=nu.T.dot(g) # Gibbs free energy change, both reactions

y0=zeros(len(names))
y0_ch4=1/(1+sc)
nu_ch4=nu[2-1,0]
y0[2-1]=1/(1+sc) # methane
y0[5-1]=sc/(1+sc) # steam

y_dr=(y0+y0[2-1]*nu[:,0]/-nu[2-1,0]*X_ch4)/(1+y0[2-1]*sum(nu[:,0])/-nu[2-1,0]*X_ch4) # exit composition of DR
K=exp(-delta_r_g/(R*T))
a=1-K[1]
b=y_dr[3-1]/y_dr[5-1]+y_dr[1-1]/y_dr[5-1]+K[1]*(y_dr[4-1]/y_dr[5-1]+1)
c=y_dr[1-1]*y_dr[3-1]/y_dr[5-1]**2-K[1]*y_dr[4-1]/y_dr[5-1]
r1,r2=[(-b+sqrt(b**2-4*a*c))/(2*a),(-b-sqrt(b**2-4*a*c))/(2*a)]
X_h2o_wgs=r1
y_dr_wgs=(y_dr+y_dr[5-1]*nu[:,1]/-nu[5-1,1]*X_h2o_wgs)/(1+y_dr[5-1]*sum(nu[:,1])/-nu[5-1,1]*X_h2o_wgs) # exit composition of WGS after DR
print('{:10.6s}{:10.10s}{:10.10s}'.format('i','y_dr_i','y_WGS_i'))
for i,name in enumerate(names):
    print('{:10.6s}{:10.5g}{:10.5g}'.format(name,y_dr[i],y_dr_wgs[i]))
print('\nX(CH_4,DR)={:0.5g}, X(H_2O,WGS)={:0.5g}'.format(X_ch4,X_h2o_wgs))
```

    i         y_dr_i    y_WGS_i
    H2 REF       0.16667   0.21968
    CH4          0.22222   0.22222
    CO2                0   0.05301
    CO          0.055556 0.0025456
    H2O          0.55556   0.50255
    N2  RE             0         0
    O2 REF             0         0
    AR REF             0         0
    He REF             0         0
    C6H6               0         0
    C6H12,             0         0
    C6H14,             0         0
    CH3OH              0         0
    C2H4O2             0         0
    HCOOH              0         0
    C2H6               0         0
    C2H4               0         0
    C3H8               0         0
    C3H6 p             0         0
    H2S an             0         0
    SO3                0         0
    SO2                0         0

    X(CH_4,DR)=0.2, X(H_2O,WGS)=0.095418

```python
f=lambda X_h2o_wgs: K[1]-prod((y_dr+y_dr[5-1]*nu[:,1]/-nu[5-1,1]*X_h2o_wgs)**nu[:,1])
x=linspace(0,0.8,200)
plt.plot(x,[f(x_var) for x_var in x])
plt.xlabel('$X_{H_2O, WGS}$')
plt.ylabel(r'$K(T)-\Pi_i(y_i p/p^0)^{\nu_i}$');
```

```python
[f(0.0975),f(0.99)]
```

    [-8.074363783167664, 152.59730355786436]

```python
bisect(lambda X_h2o_wgs: K[1]-prod((y_dr+y_dr[5-1]*nu[:,1]/-nu[5-1,1]*X_h2o_wgs)**nu[:,1]),0.0975,0.99)
```

    0.09999999999872217

```python
a=1-K[1]
b=y_dr[3-1]/y_dr[5-1]+y_dr[1-1]/y_dr[5-1]+K[1]*(y_dr[4-1]/y_dr[5-1]+1)
c=y_dr[1-1]*y_dr[3-1]/y_dr[5-1]**2-K[1]*y_dr[4-1]/y_dr[5-1]
r1,r2=[(-b+sqrt(b**2-4*a*c))/(2*a),(-b-sqrt(b**2-4*a*c))/(2*a)]
[r1,r2,f(r1),f(r2)]
```

    [0.09541797107625707,
     1.1773592172360177,
     9.237055564881302e-14,
     8.881784197001252e-15]

```python
sc=2.2 # steam to carbon
X_ch4=0.5 # CH4 conversion
T=(624+273.15) # temperature in DR

nu=zeros([len(names),2]) # stoich. coefficients
nu[:5,0]=[3,-1,0,1,-1] # H2O+CH4->CO+3H2
nu[:5,1]=[1,0,1,-1,-1] # CO+H2O->CO2+H2

h,s,g,cp=therm_ig(T) # ideal gas therm. properties
delta_r_g=nu.T.dot(g) # Gibbs free energy change, both reactions

y0=zeros(len(names))
y0_ch4=1/(1+sc)
nu_ch4=nu[2-1,0]
y0[2-1]=1/(1+sc) # methane
y0[5-1]=sc/(1+sc) # steam

y_dr=(y0+y0[2-1]*nu[:,0]/-nu[2-1,0]*X_ch4)/(1+y0[2-1]*sum(nu[:,0])/-nu[2-1,0]*X_ch4) # exit composition of DR
K=exp(-delta_r_g/(R*T))
a=1-K[1]
b=y_dr[3-1]/y_dr[5-1]+y_dr[1-1]/y_dr[5-1]+K[1]*(y_dr[4-1]/y_dr[5-1]+1)
c=y_dr[1-1]*y_dr[3-1]/y_dr[5-1]**2-K[1]*y_dr[4-1]/y_dr[5-1]
r1,r2=[(-b+sqrt(b**2-4*a*c))/(2*a),(-b-sqrt(b**2-4*a*c))/(2*a)]
X_h2o_wgs=r1
y_dr_wgs=(y_dr+y_dr[5-1]*nu[:,1]/-nu[5-1,1]*X_h2o_wgs)/(1+y_dr[5-1]*sum(nu[:,1])/-nu[5-1,1]*X_h2o_wgs) # exit composition of WGS after DR
print('{:10.6s}{:10.10s}{:10.10s}'.format('i','y_dr_i','y_WGS_i'))
for i,name in enumerate(names):
    print('{:10.6s}{:10.5g}{:10.5g}'.format(name,y_dr[i],y_dr_wgs[i]))
print('\nX(CH_4,DR)={:0.5g}, X(H_2O,WGS)={:0.5g}'.format(X_ch4,X_h2o_wgs))
```

    i         y_dr_i    y_WGS_i
    H2 REF       0.35714   0.45919
    CH4          0.11905   0.11905
    CO2                0   0.10204
    CO           0.11905  0.017004
    H2O          0.40476   0.30272
    N2  RE             0         0
    O2 REF             0         0
    AR REF             0         0
    He REF             0         0
    C6H6               0         0
    C6H12,             0         0
    C6H14,             0         0
    CH3OH              0         0
    C2H4O2             0         0
    HCOOH              0         0
    C2H6               0         0
    C2H4               0         0
    C3H8               0         0
    C3H6 p             0         0
    H2S an             0         0
    SO3                0         0
    SO2                0         0

    X(CH_4,DR)=0.5, X(H_2O,WGS)=0.25211

```python
sc=2.2 # steam to carbon
X_ch4=0.8 # CH4 conversion
T=(624+273.15) # temperature in DR

nu=zeros([len(names),2]) # stoich. coefficients
nu[:5,0]=[3,-1,0,1,-1] # H2O+CH4->CO+3H2
nu[:5,1]=[1,0,1,-1,-1] # CO+H2O->CO2+H2

h,s,g,cp=therm_ig(T) # ideal gas therm. properties
delta_r_g=nu.T.dot(g) # Gibbs free energy change, both reactions

y0=zeros(len(names))
y0_ch4=1/(1+sc)
nu_ch4=nu[2-1,0]
y0[2-1]=1/(1+sc) # methane
y0[5-1]=sc/(1+sc) # steam

y_dr=(y0+y0[2-1]*nu[:,0]/-nu[2-1,0]*X_ch4)/(1+y0[2-1]*sum(nu[:,0])/-nu[2-1,0]*X_ch4) # exit composition of DR
K=exp(-delta_r_g/(R*T))
a=1-K[1]
b=y_dr[3-1]/y_dr[5-1]+y_dr[1-1]/y_dr[5-1]+K[1]*(y_dr[4-1]/y_dr[5-1]+1)
c=y_dr[1-1]*y_dr[3-1]/y_dr[5-1]**2-K[1]*y_dr[4-1]/y_dr[5-1]
r1,r2=[(-b+sqrt(b**2-4*a*c))/(2*a),(-b-sqrt(b**2-4*a*c))/(2*a)]
X_h2o_wgs=r1
y_dr_wgs=(y_dr+y_dr[5-1]*nu[:,1]/-nu[5-1,1]*X_h2o_wgs)/(1+y_dr[5-1]*sum(nu[:,1])/-nu[5-1,1]*X_h2o_wgs) # exit composition of WGS after DR
print('{:10.6s}{:10.10s}{:10.10s}'.format('i','y_dr_i','y_WGS_i'))
for i,name in enumerate(names):
    print('{:10.6s}{:10.5g}{:10.5g}'.format(name,y_dr[i],y_dr_wgs[i]))
print('\nX(CH_4,DR)={:0.5g}, X(H_2O,WGS)={:0.5g}'.format(X_ch4,X_h2o_wgs))
```

    i         y_dr_i    y_WGS_i
    H2 REF           0.5   0.61946
    CH4         0.041667  0.041667
    CO2                0   0.11946
    CO           0.16667  0.047207
    H2O          0.29167   0.17221
    N2  RE             0         0
    O2 REF             0         0
    AR REF             0         0
    He REF             0         0
    C6H6               0         0
    C6H12,             0         0
    C6H14,             0         0
    CH3OH              0         0
    C2H4O2             0         0
    HCOOH              0         0
    C2H6               0         0
    C2H4               0         0
    C3H8               0         0
    C3H6 p             0         0
    H2S an             0         0
    SO3                0         0
    SO2                0         0

    X(CH_4,DR)=0.8, X(H_2O,WGS)=0.40958

## Known conversion in steam reforming and water-gas shift

Determine $T_{DR}$ for known conversions of both reactions, assuming WGS is in equilibrium.

Parameters:
* $X_{CH_4}=0.5$
* $X_{H_2O,WGS}=0.2148$
* $S/C=2.2$

```python
def f(T,K):
    h,s,g,cp=therm_ig(T) # ideal gas therm. properties
    delta_r_g=nu[:,1].T.dot(g) # Gibbs free energy change, both reactions
    return K-exp(-delta_r_g/(R*T))
```

```python
sc=2.2 # steam to carbon
X_ch4=0.5 # CH4 conversion

nu=zeros([len(names),2]) # stoich. coefficients
nu[:5,0]=[3,-1,0,1,-1] # H2O+CH4->CO+3H2
nu[:5,1]=[1,0,1,-1,-1] # CO+H2O->CO2+H2

y0=zeros(len(names))
y0_ch4=1/(1+sc)
nu_ch4=nu[2-1,0]
y0[2-1]=1/(1+sc) # methane
y0[5-1]=sc/(1+sc) # steam

y_dr=(y0+y0[2-1]*nu[:,0]/-nu[2-1,0]*X_ch4)/(1+y0[2-1]*sum(nu[:,0])/-nu[2-1,0]*X_ch4) # exit composition of DR

X_h2o_wgs=0.08694375/y_dr[5-1]
K=prod((y_dr+y_dr[5-1]*nu[:,1]/-nu[5-1,1]*X_h2o_wgs)**nu[:,1])
T_wgs=-34783/8.3145/(-31.762/8.3145-log(K))-273.15

print('\nX(CH_4,DR)={:0.5g}, X(H_2O,WGS)={:0.5g}, T(WGS)={:0.5g} °C'.format(X_ch4,X_h2o_wgs,T_wgs))
y_dr_wgs=(y_dr+y_dr[5-1]*nu[:,1]/-nu[5-1,1]*X_h2o_wgs)/(1+y_dr[5-1]*sum(nu[:,1])/-nu[5-1,1]*X_h2o_wgs) # exit composition of WGS after DR
print('{:10.6s}{:10.10s}{:10.10s}'.format('i','y_dr_i','y_WGS_i'))
for i,name in enumerate(names):
    print('{:10.6s}{:10.5g}{:10.5g}'.format(name,y_dr[i],y_dr_wgs[i]))
```

    X(CH_4,DR)=0.5, X(H_2O,WGS)=0.2148, T(WGS)=539.02 °C
    i         y_dr_i    y_WGS_i
    H2 REF       0.35714   0.44409
    CH4          0.11905   0.11905
    CO2                0  0.086944
    CO           0.11905  0.032104
    H2O          0.40476   0.31782
    N2  RE             0         0
    O2 REF             0         0
    AR REF             0         0
    He REF             0         0
    C6H6               0         0
    C6H12,             0         0
    C6H14,             0         0
    CH3OH              0         0
    C2H4O2             0         0
    HCOOH              0         0
    C2H6               0         0
    C2H4               0         0
    C3H8               0         0
    C3H6 p             0         0
    H2S an             0         0
    SO3                0         0
    SO2                0         0

```python
-34783/8.3145/(-31.762/8.3145-log(K))-273.15
```

    539.0218185242246

```python
K
```

    3.7841641473578553

```python
def g(T):
    h,s,g,cp=therm_ig(T) # ideal gas therm. properties
    return s
x=linspace(500,800,200)+273.15
y=[g(x_var)[0:3] for x_var in x]
plt.plot(x,y)
```

```python
K
```

    3.7841641473578553

```python
x=linspace(500,800,200)+273.15
y=[K-f(x_var,K) for x_var in x]
plt.plot(x-273.15,y,'o')
plt.plot(x-273.15,-(-34783+31.762*x)/(R*x))
```

```python

```

```python
T=linspace(298.15,860+273.15,30) # K

# 726,85 °C = 1000 K
DeltaG=(-395.886+0-(-192.590-200.275))*1000 # J/mol
DeltaH=(-394.623+0-(-247.857-111.983))*1000 # J/mol
DeltaS=(DeltaH-DeltaG)/(726.85+273.15) # J/mol/K

plt.semilogy(1/T,exp(-DeltaH/(R*T)+DeltaS/R),label=r'$K=e^{-\Delta_R H^\circ_{726.85°C}/(R T)+\Delta_R S^\circ_{726.85°C}/R}$') # ref. 800°C

def g(T):
    h,s,g,cp=therm_ig(T) # ideal gas therm. properties
    return nu[:,1].dot(g)
y=array([g(x_var) for x_var in T])

plt.semilogy(1/T,exp(-y/(R*T)),label=r'$polynomial$') # ref. 800°C
plt.legend();
```

```python
sc=2.2 # steam to carbon
X_ch4=0.8 # CH4 conversion

nu=zeros([len(names),2]) # stoich. coefficients
nu[:5,0]=[3,-1,0,1,-1] # H2O+CH4->CO+3H2
nu[:5,1]=[1,0,1,-1,-1] # CO+H2O->CO2+H2

y0=zeros(len(names))
y0_ch4=1/(1+sc)
nu_ch4=nu[2-1,0]
y0[2-1]=1/(1+sc) # methane
y0[5-1]=sc/(1+sc) # steam

y_dr=(y0+y0[2-1]*nu[:,0]/-nu[2-1,0]*X_ch4)/(1+y0[2-1]*sum(nu[:,0])/-nu[2-1,0]*X_ch4) # exit composition of DR
X_h2o_wgs=0.07455594/y_dr[5-1]
K=prod((y_dr+y_dr[5-1]*nu[:,1]/-nu[5-1,1]*X_h2o_wgs)**nu[:,1])
T_wgs=-34783/8.3145/(-31.762/8.3145-log(K))-273.15

(-394.623+0-(-247.857-111.983))*1000

print('\nX(CH_4,DR)={:0.5g}, X(H_2O,WGS)={:0.5g}, T(WGS)={:0.5g} °C'.format(X_ch4,X_h2o_wgs,T_wgs))
y_dr_wgs=(y_dr+y_dr[5-1]*nu[:,1]/-nu[5-1,1]*X_h2o_wgs)/(1+y_dr[5-1]*sum(nu[:,1])/-nu[5-1,1]*X_h2o_wgs) # exit composition of WGS after DR
print('{:10.6s}{:10.10s}{:10.10s}'.format('i','y_dr_i','y_WGS_i'))
for i,name in enumerate(names):
    print('{:10.6s}{:10.5g}{:10.5g}'.format(name,y_dr[i],y_dr_wgs[i]))
```

    X(CH_4,DR)=0.8, X(H_2O,WGS)=0.25562, T(WGS)=639.9 °C
    i         y_dr_i    y_WGS_i
    H2 REF           0.5   0.57456
    CH4         0.041667  0.041667
    CO2                0  0.074556
    CO           0.16667  0.092111
    H2O          0.29167   0.21711
    N2  RE             0         0
    O2 REF             0         0
    AR REF             0         0
    He REF             0         0
    C6H6               0         0
    C6H12,             0         0
    C6H14,             0         0
    CH3OH              0         0
    C2H4O2             0         0
    HCOOH              0         0
    C2H6               0         0
    C2H4               0         0
    C3H8               0         0
    C3H6 p             0         0
    H2S an             0         0
    SO3                0         0
    SO2                0         0

```python
K
```

    2.1420178207218914

```python
prod(y_dr_wgs**nu[:,1])
```

    2.1420178207218914


# Hermite Interpolator Tutorial in NRPy

## Author: Maria C. Babiuc-Hamilton
### Template courtesy Zach Etienne and Brandon Clark

### NRPy+ Source Code for this module: [hermite_interpolator.py](../edit/hermite_interpolator.py)

# Table of Contents \[Back to [top](#toc)\]
$$\label{toc}$$

This notebook is organized as follows

1. [Hermite1D](#hi1d): Cubic Hermite Interpolation on a 1D Uniform Grid
1. [Hermite2D](#hi2d): Bicubic Hermite Interpolation on a 2D Uniform Grid
1. [Hermite3D](#hi3d): Tricubic Hermite Interpolation on a 3D Uniform Grid
1. [Step 1](#himodule): The Hermite Interpolator NRPy+ module
    1. [Step 1.a](#hicoeffs_func): The `compute_hicoeffs_histencl()` function
        1. [Step 1.a.i](#exercise): Exercise: Using `compute_hicoeffs_histencl()`
    1. [Step 1.b](#hioutputc): The  `HI_outputC()` function
1. [Step 2](#latex_pdf_output): Output this notebook to $\LaTeX$-formatted PDF

# Hermite Interpolation on a 1D Uniform Grid \[Back to [top](#toc)\]
$$\label{hi1d}$$

In this tutorial, we will develop the interpolation technique using [Hermite interpolation](https://en.wikipedia.org/wiki/Bicubic_interpolation) (HI) techniques.

We will develop the cubic Hermite interpolation on a *uniform* numerical grid.
Let's start in one dimension, for proof of principle.
Since the grid is uniform, the spacing between successive grid points is constant and will denote it $h_{x}$.
Then, given a function $f(x)$ on this uniform grid, we will adopt the notation

$$
f(x_k) = f_k, ~
\frac{df(x_k)}{dx1} = d_x f_k.
$$

We want to interpolate this function onto a point $(x)$ somewhere on the line formed by the edges: $[x_{k}, x_{k+1}]$.

We will need $4$ known points, on a linear grid, starting from the left to the right:
$$[x_{k-1}, x_{k}, x_{k+1}, x_{k+2}]$$
We define the distance from each known point to the interpolating point:
$$s(x) = x - x_{k} = \xi h_x$$
where we impose $h_x=const$ and $0<\xi<1.$

HI techniques are usually constructed as follows:
We fit a smooth polynomial *to both the function and its derivative* at the point $x$:
$$
\begin{array}\\
P(x) & = f(x)  = \sum_{i=0}^3 c_{i} s^i \\
\frac{d}{dx}P(x) & = \frac{df(x)}{dx}  = \sum_{i=1}^3 i c_{i} s^{i-1}
\end{array}
$$

The interpolation problem consists of solving this system, to determe the $4$ unknown coefficients:
$$c_{0}, c_{1}, c_{2}, c_{3}.$$
To accomplish this, we form the system of $4$ equations by writing the above equations at the edges:
$$
\begin{array}\\
P(x_{k})             & = f_k   = \sum_{i=0}^3 c_{i} s(x_{k})^i \\
P(x_{k+1})           & = f_{k+1} = \sum_{i=0}^3 c_{i} s(x_{k+1})^i \\
\frac{d}{dx}P(x_{k}) & = d_x f_k = \sum_{i=1}^3 i c_{i} s(x_{k})^{i-1} \\
\frac{d}{dx}P(x_{k+1})& = d_x f_{k+1}= \sum_{i=1}^3 i c_{i} s(x_{k+1})^{i-1}
\end{array}
$$
Noting that: $s(x_{k}) = x_{k} - x_{k} = 0$ and $s(x_{k+1}) = x_{k+1} - x_{k} = h_x$, thus the system of 4 equations with 4 unknowns simplifies to:
$$
\left\{
    \begin{array}\\
f_k         &= c_{0}\\
f_{k+1}     &= c_{0} + c_{1}h + c_{2}h^2 + c_{3}h^3\\
d_x f_k     &= c_{1}\\
d_x f_{k+1} &= c_{1} + 2c_{2}h + 3c_{3}h^2
\end{array}
\right.
$$

There are multiple ways to compute the Hermite interpolation coefficients $c_i$.
We solve this system in Mathematica and obtain the solutions:
$$
\begin{array} \\
c_0 &= f_k \\
c_1 &= d_x f_k \\
c_2 &= \frac{3 f_{k+1} - 3 f_{k} - 2 d_x f_k h_x - d_x f_{k+1} h_x}{h_x^2} \\
c_3 &= \frac{- 2 f_{k+1} + 2 f_{k} + d_x f_k h_x + d_x f_{k+1} h_x}{h_x^3}.
\end{array}
$$

The key now is to impose the condition onto the numerical derivative of the function at the edges, such that the function stays smooth, at least locally.
This condition can be written as:
$$
d_x f_{k} = \left\{
\begin{array}\\
0     & \mbox{if } \ \text{sgn}(\delta x_{k−1})\neq \text{sgn}(\delta x_{k})\\
dx_{k}& \mbox{if } \ \text{sgn}(\delta x_{k−1})=\text{sgn}(\delta x_{k})
\end{array}
\right.
$$
and
$$
d_x f_{k+1} = \left\{
\begin{array}\\
0        & \mbox{if } \ \text{sgn}(\delta x_{k})\neq \text{sgn}(\delta x_{k+1})\\
dx_{k+1} & \mbox{if } \ \text{sgn}(\delta x_{k})=\text{sgn}(\delta x_{k+1}).
\end{array}
\right.
$$

The conditions above make use of the following quantities that must be calculated:
$$
\delta x_{k-1} = \frac{f_{k  }-f_{k-1}}{h_x},~
\delta x_{k  } = \frac{f_{k+1}-f_{k  }}{h_x},~
\delta x_{k+1} = \frac{f_{k+2}-f_{k+1}}{h_x}
$$
and:
$$
dx_{k  } =\frac{2 \delta x_{k  } \delta x(k-1)}{\delta x_{k  } + \delta x_{k-1}},~
dx_{k+1} =\frac{2 \delta x_{k+1} \delta x{k  }}{\delta x_{k+1} + \delta x_{k  }}.
$$

Once the slopes are known, the derivatives of the functions are replaced with:
$$d_x f_{k} = dx_k,~d_x f_{k+1} = d x_{k+1}.$$

We also substitute
$$
f_{k+1} = f_k + h_x \delta x_k,~
s(x) =\xi h_x
$$
to simplify the expressions for the Hermite coefficients.
Now we form the interpolation polynomial:
$$
f(x) = P(x) = \sum_{i=0}^3 c_i s^i = \sum_{i=0}^3 C_i \xi^i
$$
where the final form of the 1D Hermite coefficients is:
$$
\begin{array} \\
C_0 &= f_k \\
C_1 &= h_x d x_k\\
C_2 &=  -2 h_x d x_k - h_x d x_{k+1} + 3 h_x \delta x_k\\
C_3 &= h_x d x_k + h_x d x_{k+1} - 2 h_x \delta x_k.
\end{array}
$$
**We must simply compute $(f_k, \delta x_k, d x_k)$ at each point on the grid, impose the continuity condition, and then compute the coefficients $C_i$.**

**Recommended: Learn more about the algorithm NRPy+ adopts to automatically compute finite difference derivatives: ([How NRPy+ Computes Finite Difference Coefficients](Tutorial-How_NRPy_Computes_Finite_Difference_Coeffs.ipynb))**

Let's solve the system in *python*.
We use: https://scaron.info/blog/solving-symbolic-equations-with-sympy.html https://problemsolvingwithpython.com/10-Symbolic-Math/10.03-Defining-Variables/
https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.ode.html

```python
import matplotlib.pyplot as plt
import numpy as np
import sympy as sym
#from scipy.optimize import fsolve
```

```python
# define the variables
h = sym.var('h')
c0, c1, c2, c3 = sym.var('c0 c1 c2 c3')
fk, fkp1, dxfk, dxfkp1 = sym.var('fk fkp1 dxfk dxfkp1')

#Define the equations
E0 = -fk + c0
E1 = -fkp1 + c1*h + c2*h**2 + c3*h**3
E2 = -dxfk + c1
E3 = -dxfkp1 + c1 +2* c2*h + 3*c3*h**2

#solve the system
solv = sym.solve([E0, E1, E2, E3], [c0, c1, c2, c3])
print ("c0 = ", solv[c0].factor())
print ("c1 = ", solv[c1].factor())
print ("c2 = ", solv[c2].factor())
print ("c3 = ", solv[c3].factor())
```

    c0 =  fk
    c1 =  dxfk
    c2 =  -(2*dxfk*h + dxfkp1*h - 3*fkp1)/h**2
    c3 =  (dxfk*h + dxfkp1*h - 2*fkp1)/h**3

```python
# write the solutions in readable form
#print ("\\begin{array} \\\\")
#print ("c_0 & = %s\\\\" % sym.latex((solv[c0]).factor()))
#print ("c_1 & = %s\\\\" % sym.latex((solv[c1]).factor()))
#print ("c_2 & = %s\\\\" % sym.latex((solv[c2]).factor()))
#print ("c_3 & = %s"     % sym.latex((solv[c3]).factor()))
#print ("\\end{array}")
```

    \begin{array} \\
    c_0 & = fk\\
    c_1 & = dxfk\\
    c_2 & = - \frac{2 dxfk h + dxfkp_{1} h - 3 fkp_{1}}{h^{2}}\\
    c_3 & = \frac{dxfk h + dxfkp_{1} h - 2 fkp_{1}}{h^{3}}
    \end{array}

\begin{array} \\
c_0 & = fk\\
c_1 & = dxfk\\
c_2 & = - \frac{2 dxfk h + dxfkp_{1} h - 3 fkp_{1}}{h^{2}}\\
c_3 & = \frac{dxfk h + dxfkp_{1} h - 2 fkp_{1}}{h^{3}}
\end{array}

```python
# define the symbols
h, c0, c1, c2, c3 = sym.symbols('h c0 c1 c2 c3')
fk, fkp1, dxfk, dxfkp1 = sym.symbols('fk fkp1 dxfk dxfkp1')

#Define the equations
E0 = sym.Eq(c0, fk)
E1 = sym.Eq(c1*h + c2*h**2 + c3*h**3,fkp1)
E2 = sym.Eq(c1,dxfk)
E3 = sym.Eq(+ c1 +2* c2*h + 3*c3*h**2,dxfkp1)

#Solve
sym.solve([E0, E1, E2, E3], [c0, c1, c2, c3])

print ("c0 = ", sols[c0].factor())
print ("c1 = ", sols[c1].factor())
print ("c2 = ", sols[c2].factor())
print ("c3 = ", sols[c3].factor())
```

```python
h = sym.var('h')
Ci = sym.Matrix([[1,0,0,0],[1,h,h**2,h**3],[0,1,0,0],[0,1, 2*h,3*h**2,]])
F = sym.Matrix([fk, fkp1, dxfk, dxfkp1])
sym.pprint(Ci)
sym.pprint(Ci.inv()*F)
```

```python
solM = C.LUsolve(F)
c0 = sym.pprint(solM[0].factor())
c1 = sym.pprint(solM[1].factor())
c2 = sym.pprint(solM[2].factor())
c3 = sym.pprint(solM[3].factor())
```

# Hermite Interpolation on a 2D Uniform Grid \[Back to [top](#toc)\]
$$\label{hi2d}$$

A *bicubic* Hermite interpolation extends the 1D cubic interpolation to a 2D grid.

We consider a stencil of $4\times 4=16$ known points, on a uniform 2D grid, starting from the lower left corner us, and then advancing right:

$$
[x_{k-1},y_{k-1}, x_{k-1},y_{k}, x_{k-1},y_{k+1}, x_{k-1},y_{k+2}, \\
 x_{k  },y_{k-1}, x_{k  },y_{k}, x_{k  },y_{k+1}, x_{k  },y_{k+2}, \\
 x_{k+1},y_{k-1}, x_{k+1},y_{k}, x_{k+1},y_{k+1}, x_{k+1},y_{k+2}, \\
 x_{k+1},y_{k-1}, x_{k+2},y_{k}, x_{k+2},y_{k+1}, x_{k+2},y_{k+1}] .
$$

We interpolate onto the 2D point $(x,y)$ in the middle of the rectangle formed by the $4$ corners starting lower-left and going counterclockwise:
$$
[x_{k  },y_{k  }, x_{k  },y_{k+1},\\
 x_{k+1},y_{k+1}, x_{k+1},y_{k  }].
 $$

The Hermite interpolator is:
$$P(x,y) = \sum_{i=0}^3 \sum_{j=0}^3 c_{ij} s_x^i s_y^j,$$
where $s_x$ is the distance in the $x$ direction from the point of interpolation $(x,y)$ to $x_k$, namely:
$$s_x(x) = x - x_k = \xi h_x  $$
and $s_y$ is the distance in the $y$ direction from point of interpolation $(x,y)$ to $y_k$:
$$s_y(y) = y - y_k = \mu h_y.$$

We now form the system of $16$ equations with 16 unknowns. We have $4$ equations for the corners:
$$
\begin{array} \\
P(x_{k  },y_{k  }) &= f_{k  ,k  } = \sum_{i=0}^3 \sum_{j=0}^3 c_{ij} s_x(x_{k  })^i s_y(y_{k  })^j,\\
P(x_{k  },y_{k+1}) &= f_{k  ,k+1} = \sum_{i=0}^3 \sum_{j=0}^3 c_{ij} s_x(x_{k  })^i s_y(y_{k+1})^j\\
P(x_{k+1},y_{k+1}) &= f_{k+1,k+1} = \sum_{i=0}^3 \sum_{j=0}^3 c_{ij} s_x(x_{k+1})^i s_y(y_{k+1})^j\\
P(x_{k+1},y_{k  }) &= f_{k+1,k  } = \sum_{i=0}^3 \sum_{j=0}^3 c_{ij} s_x(x_{k+1})^i s_y(y_{k  })^j
\end{array}
$$

Next we have $4$ equations for the $x$ derivative at the $4$ corners:
$$
\begin{array} \\
\frac{d}{dx}P(x_{k  },y_{k  }) & = d_x f_{k  ,k  } = \sum_{i=1}^3 \sum_{j=0}^3 i c_{ij}
s_x(x_{k  })^{i-1} s_y(y_{k  })^j\\
\frac{d}{dx}P(x_{k  },y_{k+1}) & = d_x f_{k  ,k+1} = \sum_{i=1}^3 \sum_{j=0}^3 i c_{ij}
s_x(x_{k  })^{i-1} s_y(y_{k+1})^j\\
\frac{d}{dx}P(x_{k+1},y_{k+1}) & = d_x f_{k+1,k+1} = \sum_{i=1}^3 \sum_{j=0}^3 i c_{ij}
s_x(x_{k+1})^{i-1} s_y(y_{k+1})^j\\
\frac{d}{dx}P(x_{k+1},y_{k  }) & = d_x f_{k+1,k  } = \sum_{i=1}^3 \sum_{j=0}^3 i c_{ij}
s_x(x_{k+1})^{i-1} s_y(y_{k  })^j
\end{array}
$$

We are adding now $4$ equations for the $y$ derivative at the $4$ corners:
$$
\begin{array} \\
\frac{d}{dy}P(x_{k  },y_{k  }) & = d_y f_{k  ,k  } = \sum_{i=0}^3 \sum_{j=1}^3 j c_{ij}
s_x(x_{k  })^{i} s_y(y_{k  })^{j-1} \\
\frac{d}{dy}P(x_{k  },y_{k+1}) & = d_y f_{k  ,k+1} = \sum_{i=0}^3 \sum_{j=1}^3 j c_{ij}
s_x(x_{k  })^{i} s_y(y_{k+1})^{j-1} \\
\frac{d}{dy}P(x_{k+1},y_{k+1}) & = d_y f_{k+1,k+1} = \sum_{i=0}^3 \sum_{j=1}^3 j c_{ij}
s_x(x_{k+1})^{i} s_y(y_{k+1})^{j-1} \\
\frac{d}{dy}P(x_{k+1},y_{k  }) & = d_y f_{k+1,k  } = \sum_{i=0}^3 \sum_{j=1}^3 j c_{ij}
s_x(x_{k+1})^{i} s_y(y_{k  })^{j-1}
\end{array}
$$

Up to now we have $12$ equations. The last $4$ equations are obtained by adding to this the mixed derivatives:
$\frac{d^2}{dxdy}P(x,y) = f_{xy} = \sum_{i=1}^3 \sum_{j=1}^3 ij c_{ij} s_x^{i-1} s_y^{j-1}$, which becomes at the $4$ corners:

$$
\begin{array} \\
\frac{d^2}{dxdy}P(x_{k  },y_{k  }) & = d_{xy} f_{k  ,k  } = \sum_{i=1}^3 \sum_{j=1}^3 ij c_{ij}
s_x(x_{k  })^{i-1} s_y(y_{k  })^{j-1}\\
\frac{d^2}{dxdy}P(x_{k  },y_{k+1}) & = d_{xy} f_{k  ,k+1} = \sum_{i=1}^3 \sum_{j=1}^3 ij c_{ij}
s_x(x_{k  })^{i-1} s_y(y_{k+1})^{j-1}\\
\frac{d^2}{dxdy}P(x_{k+1},y_{k+1}) & = d_{xy} f_{k+1,k+1} = \sum_{i=1}^3 \sum_{j=1}^3 ij c_{ij}
s_x(x_{k+1})^{i-1} s_y(y_{k+1})^{j-1}\\
\frac{d^2}{dxdy}P(x_{k+1},y_{k  }) & = d_{xy} f_{k+1,k  } = \sum_{i=1}^3 \sum_{j=1}^3 ij c_{ij}
s_x(x_{k+1})^{i-1} s_y(y_{k  })^{j-1}
\end{array}
$$

```python
import sympy as sym
# define the variables
hx, hy = sym.var('hx, hy')
x, y, xk, yk = sym.var('x, y, xk, yk')
c00, c01, c02, c03 = sym.var('c00 c01 c02 c03')
c10, c11, c12, c13 = sym.var('c10 c11 c12 c13')
c20, c21, c22, c23 = sym.var('c20 c21 c22 c23')
c30, c31, c32, c33 = sym.var('c30 c31 c32 c33')
```

```python
sx = x - xk
sy = y - yk
i, j = sym.var('i, j')
cij = sym.Matrix([[c00, c01, c02, c03],[c10, c11, c12, c13],[c20, c21, c22, c23],[c30, c31, c32, c33]])
#sym.pprint(cij)

Sum00 = sym.summation(cij[i, j]*sx.subs(x,xk)**i*sy.subs(y,yk)**j, (j, 0, 3), (i, 0, 3))
Sum01 = sym.summation(cij[i, j]*sx.subs(x,xk)**i*sy.subs(y,yk+hy)**j, (j, 0, 3), (i, 0, 3))
Sum02 = sym.summation(cij[i, j]*sx.subs(x,xk+hx)**i*sy.subs(y,yk+hy)**j, (j, 0, 3), (i, 0, 3))
Sum03 = sym.summation(cij[i, j]*sx.subs(x,xk+hx)**i*sy.subs(y,yk)**j, (j, 0, 3), (i, 0, 3))

Sum10 = sym.summation((i+1)*cij[i+1, j]*sx.subs(x,xk)**i*sy.subs(y,yk)**j, (j, 0, 3), (i, 0, 2))
Sum11 = sym.summation((i+1)*cij[i+1, j]*sx.subs(x,xk)**i*sy.subs(y,yk+hy)**j, (j, 0, 3), (i, 0, 2))
Sum12 = sym.summation((i+1)*cij[i+1, j]*sx.subs(x,xk+hx)**i*sy.subs(y,yk+hy)**j, (j, 0, 3), (i, 0, 2))
Sum13 = sym.summation((i+1)*cij[i+1, j]*sx.subs(x,xk+hx)**i*sy.subs(y,yk)**j, (j, 0, 3), (i, 0, 2))

Sum20 = sym.summation((j+1)*cij[i, j+1]*sx.subs(x,xk)**i*sy.subs(y,yk)**j, (j, 0, 2), (i, 0, 3))
Sum21 = sym.summation((j+1)*cij[i, j+1]*sx.subs(x,xk)**i*sy.subs(y,yk+hy)**j, (j, 0, 2), (i, 0, 3))
Sum22 = sym.summation((j+1)*cij[i, j+1]*sx.subs(x,xk+hx)**i*sy.subs(y,yk+hy)**j, (j, 0, 2), (i, 0, 3))
Sum23 = sym.summation((j+1)*cij[i, j+1]*sx.subs(x,xk+hx)**i*sy.subs(y,yk)**j, (j, 0, 2), (i, 0, 3))

Sum30 = sym.summation((i+1)*(j+1)*cij[i+1, j+1]*sx.subs(x,xk)**i*sy.subs(y,yk)**j, (j, 0, 2), (i, 0, 2))
Sum31 = sym.summation((i+1)*(j+1)*cij[i+1, j+1]*sx.subs(x,xk)**i*sy.subs(y,yk+hy)**j, (j, 0, 2), (i, 0, 2))
Sum32 = sym.summation((i+1)*(j+1)*cij[i+1, j+1]*sx.subs(x,xk+hx)**i*sy.subs(y,yk+hy)**j, (j, 0, 2), (i, 0, 2))
Sum33 = sym.summation((i+1)*(j+1)*cij[i+1, j+1]*sx.subs(x,xk+hx)**i*sy.subs(y,yk)**j, (j, 0, 2), (i, 0, 2))

print ("Sum33 = ", Sum33)
```

```python
fkk, fkkp1, fkp1kp1, fkp1k             = sym.var('fkk fkkp1 fkp1kp1 fkp1k')
dxfkk, dxfkkp1, dxfkp1kp1, dxfkp1k     = sym.var('dxfkk dxfkkp1 dxfkp1kp1 dxfkp1k')
dyfkk, dyfkkp1, dyfkp1kp1, dyfkp1k     = sym.var('dyfkk dyfkkp1 dyfkp1kp1 dyfkp1k')
dxyfkk, dxyfkkp1, dxyfkp1kp1, dxyfkp1k = sym.var('dxyfkk dxyfkkp1 dxyfkp1kp1 dxyfkp1k')

#Define the equations
E00 = -fkk + Sum00.factor()
E01 = -fkkp1 + Sum01.factor()
E02 = -fkp1kp1 + Sum02.factor()
E03 = -fkp1k + Sum03.factor()

E10 = -dxfkk + Sum10.factor()
E11 = -dxfkkp1 + Sum11.factor()
E12 = -dxfkp1kp1 + Sum12.factor()
E13 = -dxfkp1k + Sum13.factor()

E20 = -dyfkk + Sum20.factor()
E21 = -dyfkkp1 + Sum21.factor()
E22 = -dyfkp1kp1 + Sum22.factor()
E23 = -dyfkp1k + Sum23.factor()

E30 = -dxyfkk + Sum30.factor()
E31 = -dxyfkkp1 + Sum31.factor()
E32 = -dxyfkp1kp1 + Sum32.factor()
E33 = -dxyfkp1k + Sum33.factor()

print(E33)

#solve the system
solv2D = sym.solve([E00,E01,E02,E03, E10,E11,E12,E13, E20,E21,E22,E23, E30,E31,E32,E33], [c00,c01,c02,c03, c10,c11,c12,c13, c20,c21,c22,c23, c30,c31,c32,c33])
print ("c33 = ", solv2D[c33].factor())
print ("c3 = ", solv[c3].factor())
```

```python
PSum01 = sym.Poly(Sum01, c00,c01,c02,c03, c10,c11,c12,c13, c20,c21,c22,c23, c30,c31,c32,c33)
CSum01 = PSum01.coeffs()
MSum01 = PSum01.monoms()
MCSum01 = sym.Matrix(MSum01coeffs)
MMSum01 = sym.Matrix(MSum01monoms)
sym.pprint(MCSum01.T*MMSum01)
```

# Hermite Interpolation on a 3D Uniform Grid \[Back to [top](#toc)\]
$$\label{hi2d}$$

A *tricubic* Hermite interpolation extends the 2D cubic interpolation to a 3D grid.

We consider a stencil of $4\times 4 \times 4=64$ known points, on a uniform 3D grid

We interpolate onto the 3D point $(x,y,z)$ in the middle of the cube formed by the $8$ corners starting lower-left and going counterclockwise:
$$(x_{k},y_{k},z_{k}), (x_{k},y_{k+1},z_{k}),(x_{k+1},y_{k+1},z_{k}), (x_{k+1},y_{k},z_{k})\\
(x_{k},y_{k},z_{k+1}), (x_{k},y_{k+1},z_{k+1}),(x_{k+1},y_{k+1},z_{k+1}), (x_{k+1},y_{k},z_{k+1}).$$

The interpolation problem consists of determining the 64 coefficients:
$ c_{ijl} $ for $ i, j, l = (0, 3).$

The Hermite interpolator is:
$$P(x,y) = \sum_{i=0}^3 \sum_{j=0}^3 \sum_{k=0}^3 c_{ijk} s_x^i s_y^j s_z^k,$$
where
$$
s_x(x) = x - x_k = \xi h_x~,
s_y(y) = y - y_k = \mu h_y~,
s_z(z) = z - z_k = \zeta h_z.
$$

We now form the system of $64$ equations with 64 unknowns. We have $8$ equations for the corners:
$$
\begin{array} \\
P(x_{k  },y_{k  },z_{k  }) & =  f_{k  ,k  ,k  } =
\sum_{i = 0}^3 \sum_{j = 0}^3 \sum_{l = 0}^3 c_{ijl} s_x(x_{k  })^i s_y(y_{k  })^j s_z(z_{k  })^l\\
P(x_{k  },y_{k+1},z_{k  }) & =  f_{k  ,k+1,k  } =
\sum_{i = 0}^3 \sum_{j = 0}^3 \sum_{l = 0}^3 c_{ijl} s_x(x_{k  })^i s_y(y_{k+1})^j s_z(z_{k  })^l\\
P(x_{k+1},y_{k+1},z_{k  }) & =  f_{k+1,k+1,k  } =
\sum_{i = 0}^3 \sum_{j = 0}^3 \sum_{l = 0}^3 c_{ijl} s_x(x_{k+1})^i s_y(y_{k+1})^j s_z(z_{k  })^l\\
P(x_{k+1},y_{k  },z_{k  }) & =  f_{k+1,k  ,k  } =
\sum_{i = 0}^3 \sum_{j = 0}^3 \sum_{l = 0}^3 c_{ijl} s_x(x_{k+1})^i s_y(y_{k  })^j s_z(z_{k  })^l\\
P(x_{k  },y_{k  },z_{k+1}) & =  f_{k  ,k  ,k+1} =
\sum_{i = 0}^3 \sum_{j = 0}^3 \sum_{l = 0}^3 c_{ijl} s_x(x_{k  })^i s_y(y_{k  })^j s_z(z_{k+1})^l\\
P(x_{k  },y_{k+1},z_{k+1}) & =  f_{k  ,k+1,k+1} =
\sum_{i = 0}^3 \sum_{j = 0}^3 \sum_{l = 0}^3 c_{ijl} s_x(x_{k  })^i s_y(y_{k+1})^j s_z(z_{k+1})^l\\
P(x_{k+1},y_{k+1},z_{k+1}) & =  f_{k+1,k+1,k+1} =
\sum_{i = 0}^3 \sum_{j = 0}^3 \sum_{l = 0}^3 c_{ijl} s_x(x_{k+1})^i s_y(y_{k+1})^j s_z(z_{k+1})^l\\
P(x_{k+1},y_{k  },z_{k+1}) & =  f_{k+1,k  ,k+1} =
\sum_{i = 0}^3 \sum_{j = 0}^3 \sum_{l = 0}^3 c_{ijl} s_x(x_{k+1})^i s_y(y_{k  })^j s_z(z_{k+1})^l
\end{array}
$$

Next we have $8$ equations for the $x$ derivative at the $8$ corners:
$$
\begin{array} \\
\frac{d}{dx}P(x_{k  },y_{k  },z_{k  }) & =  d_x f_{k  ,k  ,k  } =
\sum_{i = 1}^3 \sum_{j = 0}^3 \sum_{l = 0}^3 i c_{ijl} s_x(x_{k  })^{i-1} s_y(y_{k  })^j s_z(z_{k  })^l\\
\frac{d}{dx}P(x_{k  },y_{k+1},z_{k  }) & =  d_x f_{k  ,k+1,k  } =
\sum_{i = 1}^3 \sum_{j = 0}^3 \sum_{l = 0}^3 i c_{ijl} s_x(x_{k  })^{i-1} s_y(y_{k+1})^j s_z(z_{k  })^l\\
\frac{d}{dx}P(x_{k+1},y_{k+1},z_{k  }) & =  d_x f_{k+1,k+1,k  } =
\sum_{i = 1}^3 \sum_{j = 0}^3 \sum_{l = 0}^3 i c_{ijl} s_x(x_{k+1})^{i-1} s_y(y_{k+1})^j s_z(z_{k  })^l\\
\frac{d}{dx}P(x_{k+1},y_{k  },z_{k  }) & =  d_x f_{k+1,k  ,k  } =
\sum_{i = 1}^3 \sum_{j = 0}^3 \sum_{l = 0}^3 i c_{ijl} s_x(x_{k+1})^{i-1} s_y(y_{k  })^j s_z(z_{k  })^l\\
\frac{d}{dx}P(x_{k  },y_{k  },z_{k+1}) & =  d_x f_{k  ,k  ,k+1} =
\sum_{i = 1}^3 \sum_{j = 0}^3 \sum_{l = 0}^3 i c_{ijl} s_x(x_{k  })^{i-1} s_y(y_{k  })^j s_z(z_{k+1})^l\\
\frac{d}{dx}P(x_{k  },y_{k+1},z_{k+1}) & =  d_x f_{k  ,k+1,k+1} =
\sum_{i = 1}^3 \sum_{j = 0}^3 \sum_{l = 0}^3 i c_{ijl} s_x(x_{k  })^{i-1} s_y(y_{k+1})^j s_z(z_{k+1})^l\\
\frac{d}{dx}P(x_{k+1},y_{k+1},z_{k+1}) & =  d_x f_{k+1,k+1,k+1} =
\sum_{i = 1}^3 \sum_{j = 0}^3 \sum_{l = 0}^3 i c_{ijl} s_x(x_{k+1})^{i-1} s_y(y_{k+1})^j s_z(z_{k+1})^l\\
\frac{d}{dx}P(x_{k+1},y_{k  },z_{k+1}) & =  d_x f_{k+1,k  ,k+1} =
\sum_{i = 1}^3 \sum_{j = 0}^3 \sum_{l = 0}^3 i c_{ijl} s_x(x_{k+1})^{i-1} s_y(y_{k  })^j s_z(z_{k+1})^l
\end{array}
$$

We are adding now $8$ equations for the $y$ derivative at the $8$ corners:
$$
\begin{array} \\
\frac{d}{dy}P(x_{k  },y_{k  },z_{k  }) & =  d_y f_{k  ,k  ,k  } =
\sum_{i = 0}^3 \sum_{j = 1}^3 \sum_{l = 0}^3 j c_{ijl} s_x(x_{k  })^{i} s_y(y_{k  })^{j-1} s_z(z_{k  })^l\\
\frac{d}{dy}P(x_{k  },y_{k+1},z_{k  }) & =  d_y f_{k  ,k+1,k  } =
\sum_{i = 0}^3 \sum_{j = 1}^3 \sum_{l = 0}^3 j c_{ijl} s_x(x_{k  })^{i} s_y(y_{k+1})^{j-1} s_z(z_{k  })^l\\
\frac{d}{dy}P(x_{k+1},y_{k+1},z_{k  }) & =  d_y f_{k+1,k+1,k  } =
\sum_{i = 0}^3 \sum_{j = 1}^3 \sum_{l = 0}^3 j c_{ijl} s_x(x_{k+1})^{i} s_y(y_{k+1})^{j-1} s_z(z_{k  })^l\\
\frac{d}{dy}P(x_{k+1},y_{k  },z_{k  }) & =  d_y f_{k+1,k  ,k  } =
\sum_{i = 0}^3 \sum_{j = 1}^3 \sum_{l = 0}^3 j c_{ijl} s_x(x_{k+1})^{i} s_y(y_{k  })^{j-1} s_z(z_{k  })^l\\
\frac{d}{dy}P(x_{k  },y_{k  },z_{k+1}) & =  d_y f_{k  ,k  ,k+1} =
\sum_{i = 0}^3 \sum_{j = 1}^3 \sum_{l = 0}^3 j c_{ijl} s_x(x_{k  })^{i} s_y(y_{k  })^{j-1} s_z(z_{k+1})^l\\
\frac{d}{dy}P(x_{k  },y_{k+1},z_{k+1}) & =  d_y f_{k  ,k+1,k+1} =
\sum_{i = 0}^3 \sum_{j = 1}^3 \sum_{l = 0}^3 j c_{ijl} s_x(x_{k  })^{i} s_y(y_{k+1})^{j-1} s_z(z_{k+1})^l\\
\frac{d}{dy}P(x_{k+1},y_{k+1},z_{k+1}) & =  d_y f_{k+1,k+1,k+1} =
\sum_{i = 0}^3 \sum_{j = 1}^3 \sum_{l = 0}^3 j c_{ijl} s_x(x_{k+1})^{i} s_y(y_{k+1})^{j-1} s_z(z_{k+1})^l\\
\frac{d}{dy}P(x_{k+1},y_{k  },z_{k+1}) & =  d_y f_{k+1,k  ,k+1} =
\sum_{i = 0}^3 \sum_{j = 1}^3 \sum_{l = 0}^3 j c_{ijl} s_x(x_{k+1})^{i} s_y(y_{k  })^{j-1} s_z(z_{k+1})^l
\end{array}
$$

We supplement with the $8$ equations for the $z$ derivative at the $8$ corners:
$$
\begin{array} \\
\frac{d}{dz}P(x_{k  },y_{k  },z_{k  }) & =  d_z f_{k  ,k  ,k  } =
\sum_{i = 0}^3 \sum_{j = 0}^3 \sum_{l = 1}^3 l c_{ijl} s_x(x_{k  })^{i} s_y(y_{k  })^{j} s_z(z_{k  })^{l-1}\\
\frac{d}{dz}P(x_{k  },y_{k+1},z_{k  }) & =  d_z f_{k  ,k+1,k  } =
\sum_{i = 0}^3 \sum_{j = 0}^3 \sum_{l = 1}^3 l c_{ijl} s_x(x_{k  })^{i} s_y(y_{k+1})^{j} s_z(z_{k  })^{l-1}\\
\frac{d}{dz}P(x_{k+1},y_{k+1},z_{k  }) & =  d_z f_{k+1,k+1,k  } =
\sum_{i = 0}^3 \sum_{j = 0}^3 \sum_{l = 1}^3 l c_{ijl} s_x(x_{k+1})^{i} s_y(y_{k+1})^{j} s_z(z_{k  })^{l-1}\\
\frac{d}{dz}P(x_{k+1},y_{k  },z_{k  }) & =  d_z f_{k+1,k  ,k  } =
\sum_{i = 0}^3 \sum_{j = 0}^3 \sum_{l = 1}^3 l c_{ijl} s_x(x_{k+1})^{i} s_y(y_{k  })^{j} s_z(z_{k  })^{l-1}\\
\frac{d}{dz}P(x_{k  },y_{k  },z_{k+1}) & =  d_z f_{k  ,k  ,k+1} =
\sum_{i = 0}^3 \sum_{j = 0}^3 \sum_{l = 1}^3 l c_{ijl} s_x(x_{k  })^{i} s_y(y_{k  })^{j} s_z(z_{k+1})^{l-1}\\
\frac{d}{dz}P(x_{k  },y_{k+1},z_{k+1}) & =  d_z f_{k  ,k+1,k+1} =
\sum_{i = 0}^3 \sum_{j = 0}^3 \sum_{l = 1}^3 l c_{ijl} s_x(x_{k  })^{i} s_y(y_{k+1})^{j} s_z(z_{k+1})^{l-1}\\
\frac{d}{dz}P(x_{k+1},y_{k+1},z_{k+1}) & =  d_z f_{k+1,k+1,k+1} =
\sum_{i = 0}^3 \sum_{j = 0}^3 \sum_{l = 1}^3 l c_{ijl} s_x(x_{k+1})^{i} s_y(y_{k+1})^{j} s_z(z_{k+1})^{l-1}\\
\frac{d}{dz}P(x_{k+1},y_{k  },z_{k+1}) & =  d_z f_{k+1,k  ,k+1} =
\sum_{i = 0}^3 \sum_{j = 0}^3 \sum_{l = 1}^3 l c_{ijl} s_x(x_{k+1})^{i} s_y(y_{k  })^{j} s_z(z_{k+1})^{l-1}
\end{array}
$$

Up to now we have $8\times 4 = 32$ equations.
We obtain the reas of $32$ equations by adding to this the mixed derivatives in $(x, y)$ at the $8$ corners:
$$
\begin{array} \\
\frac{d^2}{dxdy}P(x_{k  },y_{k  },z_{k  }) & =  d_{xy} f_{k  ,k  ,k  }  =
\sum_{i = 1}^3 \sum_{j = 1}^3 \sum_{l = 0}^3 ij c_{ijl} s_x(x_{k  })^{i-1} s_y(y_{k  })^{j-1} s_z(z_{k  })^{l  }\\
\frac{d^2}{dxdy}P(x_{k  },y_{k+1},z_{k  }) & =  d_{xy} f_{k  ,k+1,k  }  =
\sum_{i = 1}^3 \sum_{j = 1}^3 \sum_{l = 0}^3 ij c_{ijl} s_x(x_{k  })^{i-1} s_y(y_{k+1})^{j-1} s_z(z_{k  })^{l  }\\
\frac{d^2}{dxdy}P(x_{k+1},y_{k+1},z_{k  }) & =  d_{xy} f_{k+1,k+1,k  }  =
\sum_{i = 1}^3 \sum_{j = 1}^3 \sum_{l = 0}^3 ij c_{ijl} s_x(x_{k+1})^{i-1} s_y(y_{k+1})^{j-1} s_z(z_{k  })^{l  }\\
\frac{d^2}{dxdy}P(x_{k+1},y_{k  },z_{k  }) & =  d_{xy} f_{k+1,k  ,k  }  =
\sum_{i = 1}^3 \sum_{j = 1}^3 \sum_{l = 0}^3 ij c_{ijl} s_x(x_{k+1})^{i-1} s_y(y_{k  })^{j-1} s_z(z_{k  })^{l  }\\
\frac{d^2}{dxdy}P(x_{k  },y_{k  },z_{k+1}) & =  d_{xy} f_{k  ,k  ,k+1}  =
\sum_{i = 1}^3 \sum_{j = 1}^3 \sum_{l = 0}^3 ij c_{ijl} s_x(x_{k  })^{i-1} s_y(y_{k  })^{j-1} s_z(z_{k+1})^{l  }\\
\frac{d^2}{dxdy}P(x_{k  },y_{k+1},z_{k+1}) & =  d_{xy} f_{k  ,k+1,k+1}  =
\sum_{i = 1}^3 \sum_{j = 1}^3 \sum_{l = 0}^3 ij c_{ijl} s_x(x_{k  })^{i-1} s_y(y_{k+1})^{j-1} s_z(z_{k+1})^{l  }\\
\frac{d^2}{dxdy}P(x_{k+1},y_{k+1},z_{k+1}) & =  d_{xy} f_{k+1,k+1,k+1}  =
\sum_{i = 1}^3 \sum_{j = 1}^3 \sum_{l = 0}^3 ij c_{ijl} s_x(x_{k+1})^{i-1} s_y(y_{k+1})^{j-1} s_z(z_{k+1})^{l  }\\
\frac{d^2}{dxdy}P(x_{k+1},y_{k  },z_{k+1}) & =  d_{xy} f_{k+1,k  ,k+1}  =
\sum_{i = 1}^3 \sum_{j = 1}^3 \sum_{l = 0}^3 ij c_{ijl} s_x(x_{k+1})^{i-1} s_y(y_{k  })^{j-1} s_z(z_{k+1})^{l  }
\end{array}
$$

The $8$ equations for the mixed derivatives in $(x, z)$ at the $8$ corners:
$$
\begin{array} \\
\frac{d^2}{dxdz}P(x_{k  },y_{k  },z_{k  }) & =  d_{xz} f_{k  ,k  ,k  }  =
\sum_{i = 1}^3 \sum_{j = 0}^3 \sum_{l = 1}^3 il c_{ijl} s_x(x_{k  })^{i-1} s_y(y_{k  })^{j  } s_z(z_{k  })^{l-1}\\
\frac{d^2}{dxdz}P(x_{k  },y_{k+1},z_{k  }) & =  d_{xz} f_{k  ,k+1,k  }  =
\sum_{i = 1}^3 \sum_{j = 0}^3 \sum_{l = 1}^3 il c_{ijl} s_x(x_{k  })^{i-1} s_y(y_{k+1})^{j  } s_z(z_{k  })^{l-1}\\
\frac{d^2}{dxdz}P(x_{k+1},y_{k+1},z_{k  }) & =  d_{xz} f_{k+1,k+1,k  }  =
\sum_{i = 1}^3 \sum_{j = 0}^3 \sum_{l = 1}^3 il c_{ijl} s_x(x_{k+1})^{i-1} s_y(y_{k+1})^{j  } s_z(z_{k  })^{l-1}\\
\frac{d^2}{dxdz}P(x_{k+1},y_{k  },z_{k  }) & =  d_{xz} f_{k+1,k  ,k  }  =
\sum_{i = 1}^3 \sum_{j = 0}^3 \sum_{l = 1}^3 il c_{ijl} s_x(x_{k+1})^{i-1} s_y(y_{k  })^{j  } s_z(z_{k  })^{l-1}\\
\frac{d^2}{dxdz}P(x_{k  },y_{k  },z_{k+1}) & =  d_{xz} f_{k  ,k  ,k+1}  =
\sum_{i = 1}^3 \sum_{j = 0}^3 \sum_{l = 1}^3 il c_{ijl} s_x(x_{k  })^{i-1} s_y(y_{k  })^{j  } s_z(z_{k+1})^{l-1}\\
\frac{d^2}{dxdz}P(x_{k  },y_{k+1},z_{k+1}) & =  d_{xz} f_{k  ,k+1,k+1}  =
\sum_{i = 1}^3 \sum_{j = 0}^3 \sum_{l = 1}^3 il c_{ijl} s_x(x_{k  })^{i-1} s_y(y_{k+1})^{j  } s_z(z_{k+1})^{l-1}\\
\frac{d^2}{dxdz}P(x_{k+1},y_{k+1},z_{k+1}) & =  d_{xz} f_{k+1,k+1,k+1}  =
\sum_{i = 1}^3 \sum_{j = 0}^3 \sum_{l = 1}^3 il c_{ijl} s_x(x_{k+1})^{i-1} s_y(y_{k+1})^{j  } s_z(z_{k+1})^{l-1}\\
\frac{d^2}{dxdz}P(x_{k+1},y_{k  },z_{k+1}) & =  d_{xz} f_{k+1,k  ,k+1}  =
\sum_{i = 1}^3 \sum_{j = 0}^3 \sum_{l = 1}^3 il c_{ijl} s_x(x_{k+1})^{i-1} s_y(y_{k  })^{j  } s_z(z_{k+1})^{l-1}
\end{array}
$$

The $8$ equations for the mixed derivatives in $(y, z)$ at the $8$ corners:
$$
\begin{array} \\
\frac{d^2}{dydz}P(x_{k  },y_{k  },z_{k  }) & =  d_{yz} f_{k  ,k  ,k  }  =
\sum_{i = 0}^3 \sum_{j = 1}^3 \sum_{l = 1}^3 jl c_{ijl} s_x(x_{k  })^{i  } s_y(y_{k  })^{j-1} s_z(z_{k  })^{l-1}\\
\frac{d^2}{dydz}P(x_{k  },y_{k+1},z_{k  }) & =  d_{yz} f_{k  ,k+1,k  }  =
\sum_{i = 0}^3 \sum_{j = 1}^3 \sum_{l = 1}^3 jl c_{ijl} s_x(x_{k  })^{i  } s_y(y_{k+1})^{j-1} s_z(z_{k  })^{l-1}\\
\frac{d^2}{dydz}P(x_{k+1},y_{k+1},z_{k  }) & =  d_{yz} f_{k+1,k+1,k  }  =
\sum_{i = 0}^3 \sum_{j = 1}^3 \sum_{l = 1}^3 jl c_{ijl} s_x(x_{k+1})^{i  } s_y(y_{k+1})^{j-1} s_z(z_{k  })^{l-1}\\
\frac{d^2}{dydz}P(x_{k+1},y_{k  },z_{k  }) & =  d_{yz} f_{k+1,k  ,k  }  =
\sum_{i = 0}^3 \sum_{j = 1}^3 \sum_{l = 1}^3 jl c_{ijl} s_x(x_{k+1})^{i  } s_y(y_{k  })^{j-1} s_z(z_{k  })^{l-1}\\
\frac{d^2}{dydz}P(x_{k  },y_{k  },z_{k+1}) & =  d_{yz} f_{k  ,k  ,k+1}  =
\sum_{i = 0}^3 \sum_{j = 1}^3 \sum_{l = 1}^3 jl c_{ijl} s_x(x_{k  })^{i  } s_y(y_{k  })^{j-1} s_z(z_{k+1})^{l-1}\\
\frac{d^2}{dydz}P(x_{k  },y_{k+1},z_{k+1}) & =  d_{yz} f_{k  ,k+1,k+1}  =
\sum_{i = 0}^3 \sum_{j = 1}^3 \sum_{l = 1}^3 jl c_{ijl} s_x(x_{k  })^{i  } s_y(y_{k+1})^{j-1} s_z(z_{k+1})^{l-1}\\
\frac{d^2}{dydz}P(x_{k+1},y_{k+1},z_{k+1}) & =  d_{yz} f_{k+1,k+1,k+1}  =
\sum_{i = 0}^3 \sum_{j = 1}^3 \sum_{l = 1}^3 jl c_{ijl} s_x(x_{k+1})^{i  } s_y(y_{k+1})^{j-1} s_z(z_{k+1})^{l-1}\\
\frac{d^2}{dydz}P(x_{k+1},y_{k  },z_{k+1}) & =  d_{yz} f_{k+1,k  ,k+1}  =
\sum_{i = 0}^3 \sum_{j = 1}^3 \sum_{l = 1}^3 jl c_{ijl} s_x(x_{k+1})^{i  } s_y(y_{k  })^{j-1} s_z(z_{k+1})^{l-1}
\end{array}
$$

Lastly, the $8$ equations for the mixed derivatives in $(x, y, z)$ at the $8$ corners:
$$
\begin{array} \\
\frac{d^3}{dxdydz}P(x_{k  },y_{k  },z_{k  }) & =  d_{xyz} f_{k  ,k  ,k  }  =
\sum_{i = 1}^3 \sum_{j = 1}^3 \sum_{l = 1}^3 ijl c_{ijl} s_x(x_{k  })^{i-1} s_y(y_{k  })^{j-1} s_z(z_{k  })^{l-1}\\
\frac{d^3}{dxdydz}P(x_{k  },y_{k+1},z_{k  }) & =  d_{xyz} f_{k  ,k+1,k  }  =
\sum_{i = 1}^3 \sum_{j = 1}^3 \sum_{l = 1}^3 ijl c_{ijl} s_x(x_{k  })^{i-1} s_y(y_{k+1})^{j-1} s_z(z_{k  })^{l-1}\\
\frac{d^3}{dxdydz}P(x_{k+1},y_{k+1},z_{k  }) & =  d_{xyz} f_{k+1,k+1,k  }  =
\sum_{i = 1}^3 \sum_{j = 1}^3 \sum_{l = 1}^3 ijl c_{ijl} s_x(x_{k+1})^{i-1} s_y(y_{k+1})^{j-1} s_z(z_{k  })^{l-1}\\
\frac{d^3}{dxdydz}P(x_{k+1},y_{k  },z_{k  }) & =  d_{xyz} f_{k+1,k  ,k  }  =
\sum_{i = 1}^3 \sum_{j = 1}^3 \sum_{l = 1}^3 ijl c_{ijl} s_x(x_{k+1})^{i-1} s_y(y_{k  })^{j-1} s_z(z_{k  })^{l-1}\\
\frac{d^3}{dxdydz}P(x_{k  },y_{k  },z_{k+1}) & =  d_{xyz} f_{k  ,k  ,k+1}  =
\sum_{i = 1}^3 \sum_{j = 1}^3 \sum_{l = 1}^3 ijl c_{ijl} s_x(x_{k  })^{i-1} s_y(y_{k  })^{j-1} s_z(z_{k+1})^{l-1}\\
\frac{d^3}{dxdydz}P(x_{k  },y_{k+1},z_{k+1}) & =  d_{xyz} f_{k  ,k+1,k+1}  =
\sum_{i = 1}^3 \sum_{j = 1}^3 \sum_{l = 1}^3 ijl c_{ijl} s_x(x_{k  })^{i-1} s_y(y_{k+1})^{j-1} s_z(z_{k+1})^{l-1}\\
\frac{d^3}{dxdydz}P(x_{k+1},y_{k+1},z_{k+1}) & =  d_{xyz} f_{k+1,k+1,k+1}  =
\sum_{i = 1}^3 \sum_{j = 1}^3 \sum_{l = 1}^3 ijl c_{ijl} s_x(x_{k+1})^{i-1} s_y(y_{k+1})^{j-1} s_z(z_{k+1})^{l-1}\\
\frac{d^3}{dxdydz}P(x_{k+1},y_{k  },z_{k+1}) & =  d_{xyz} f_{k+1,k  ,k+1}  =
\sum_{i = 1}^3 \sum_{j = 1}^3 \sum_{l = 1}^3 ijl c_{ijl} s_x(x_{k+1})^{i-1} s_y(y_{k  })^{j-1} s_z(z_{k+1})^{l-1}
\end{array}
$$

 # TODO: the text below is the original from Tutorial-Finite_Difference_derivatives.ipynb

# Step 1: The finite_difference NRPy+ module \[Back to [top](#toc)\]
$$\label{fdmodule}$$

The finite_difference NRPy+ module contains one parameter:

* **FD_CENTDERIVS_ORDER**: An integer indicating the requested finite difference accuracy order (not the order of the derivative) , where FD_CENTDERIVS_ORDER = [the size of the finite difference stencil in each direction, plus one].

The finite_difference NRPy+ module contains two core functions: `compute_fdcoeffs_fdstencl()` and `FD_outputC()`. The first is a low-level function normally called only by `FD_outputC()`, which computes and outputs finite difference coefficients and the numerical grid indices (stencil) corresponding to each coefficient:

## Step 1.a:  The `compute_fdcoeffs_fdstencl()` function \[Back to [top](#toc)\]
$$\label{fdcoeffs_func}$$

**compute_fdcoeffs_fdstencl(derivstring,FDORDER=-1)**:
* Output nonzero finite difference coefficients and corresponding numerical stencil as lists, using as inputs:
    * **derivstring**: indicates the precise type and direction derivative desired:
        * **Centered derivatives**, where the center of the finite difference stencil corresponds to the point where the derivative is desired:
            * For a first-order derivative, set derivstring to "D"+"dirn", where "dirn" is an integer denoting direction. For a second-order derivative, set derivstring to "DD"+"dirn1"+"dirn2", where "dirn1" and "dirn2" are integers denoting the direction of each derivative. Currently only $1 \le N \le 2$ supported (extension to higher-order derivatives is straightforward). Examples in 3D Cartesian coordinates (x,y,z):
                * the derivative operator $\partial_x^2$ corresponds to derivstring = "DD00"
                * the derivative operator $\partial_x \partial_y$ corresponds to derivstring = "DD01"
                * the derivative operator $\partial_z$ corresponds to derivstring = "D2"
        * **Up- or downwinded derivatives**, where the center of the finite difference stencil is *one gridpoint* up or down from where the derivative is requested.
            * Set derivstring to "upD"+"dirn" or "dnD"+"dirn", where "dirn" is an integer denoting direction. Example in 3D Cartesian coordinates (x,y,z):
                * the upwinded derivative operator $\partial_x$ corresponds to derivstring = "dupD0"
        * **Kreiss-Oliger dissipation derivatives**, where the center of the finite difference stencil corresponds to the point where the dissipation will be applied.
            * Set derivstring to "dKOD"+"dirn", where "dirn" is an integer denoting direction. Example in 3D Cartesian coordinates (x,y,z):
                * the Kreiss-Oliger derivative operator $\partial_z^\text{KO}$ corresponds to derivstring = "dKOD2"
    * **FDORDER**: an *optional* parameter that, if set to a positive even integer, overrides FD_CENTDERIVS_ORDER

Within NRPy+, `compute_fdcoeffs_fdstencl()` is only called from `FD_outputC()`. Regardless, this function provides a nice interface for evaluating finite difference coefficients, as shown below:

```python
# Import the finite difference module
import finite_difference as fin  # NRPy+: Finite difference C code generation module

fdcoeffs, fdstencl = fin.compute_fdcoeffs_fdstencl("dDD00")
print(fdcoeffs)
print(fdstencl)
```

Interpreting the output, notice first that $\texttt{fdstencl}$ is a list of coordinate indices, where up to 4 dimension indices are supported (higher dimensions are possible and can be straightforwardly added, though be warned about [The Curse of Dimensionality](https://en.wikipedia.org/wiki/Curse_of_dimensionality)).

Thus NRPy+ found that for some function $u$, the fourth-order accurate finite difference operator at point $x_{i0}$ is given by

$$[\partial_{x}^{2} u]^\text{FD4}_{i0} = \frac{1}{\Delta x^{2}} \left[ -\frac{1}{12} \left(u_{i0-2,i1,i2,i3} + u_{i0+2,i1,i2,i3}\right) - \frac{5}{2}u_{i0,i1,i2,i3} + \frac{4}{3}\left(u_{i0-1,i1,i2,i3} + u_{i0+1,i1,i2,i3}\right)\right]$$

Notice also that multiplying by the appropriate power of $\frac{1}{\Delta x}$ term is up to the user of this function.

In addition, if the gridfunction $u$ exists on a grid that is less than four (spatial) dimensions, it is up to the user to truncate the additional index information.

### Step 1.a.i: Exercise: Using `compute_fdcoeffs_fdstencl()` \[Back to [top](#toc)\]
$$\label{exercise}$$

Using `compute_fdcoeffs_fdstencl()` write the necessary loops to output the finite difference coefficient tables in the Wikipedia article on [finite difference coefficients](https://en.wikipedia.org/wiki/Finite_difference_coefficients), for first and second centered derivatives (i.e., up to $\partial_i^2$)  up to eighth-order accuracy. [Solution, courtesy Brandon Clark](Tutorial-Finite_Difference_Derivatives-FDtable_soln.ipynb).

## Step 1.b: The  `FD_outputC()` function \[Back to [top](#toc)\]
$$\label{fdoutputc}$$

**FD_outputC(filename,sympyexpr_list)**: C code generator for finite-difference expressions.

C codes that evaluate expressions with finite difference derivatives on numerical grids generally consist of three  components, all existing within a loop over "interior" gridpoints; at a given gridpoint, the code must
1. Read gridfunctions from memory at all points needed to evaluate the finite difference derivatives or the gridfunctions themselves.
2. Perform arithmetic, including computation of finite difference stencils.
3. Write the output from the arithmetic to other gridfunctions.

To minimize cache misses and maximize potential compiler optimizations, it is generally recommended to segregate the above three steps. FD_outputC() first analyzes the input expressions, searching for derivatives of gridfunctions. The search is very easy, as NRPy+ requires a very specific syntax for derivatives:
* gf_dD0 denotes the first derivative of gridfunction "gf" in direction zero.
* gf_dupD0 denotes the upwinded first derivative of gridfunction "gf" in direction zero.
* gf_ddnD0 denotes the downwinded first derivative of gridfunction "gf" in direction zero.
* gf_dKOD2 denotes the Kreiss-Oliger dissipation operator of gridfunction "gf" in direction two.
Each time `FD_outputC()` finds a derivative (including references to the gridfunction directly \["zeroth"-order derivatives\]) in this way, it calls `compute_fdcoeffs_fdstencl()` to record the specific locations in memory from which the underlying gridfunction must be read to evaluate the appropriate finite difference derivative.

`FD_outputC()` then orders this list of points for all gridfunctions and points in memory, optimizing memory reads based on how the gridfunctions are stored in memory (set via parameter MemAllocStyle in the NRPy+ grid module). It then completes step 1.

For step 2, `FD_outputC()` exports all of the finite difference expressions, as well as the original expressions input into the function, to outputC() to generate the optimized C code. Step 3 follows trivally from just being careful with the bookkeeping in the above steps.

`FD_outputC()` takes two arguments:
* **filename**: Set to "stdout" to print to screen. Otherwise specify a filename.
* **sympyexpr_list**: A single named tuple or list of named tuples of type "lhrh", where the lhrh type refers to the simple structure:
    * **lhrh(left-hand side of equation, right-hand side of the equation)**

Time for an example: let's compute
$$
\texttt{output} = \text{phi_dDD00} = \partial_x^2 \phi(x,t),
$$
where $\phi$ is a function of space and time, though we only store its spatial values at a given time (*a la* the [Method of Lines](https://reference.wolfram.com/language/tutorial/NDSolveMethodOfLines.html), described & implemented in next the [Scalar Wave Equation module](Tutorial-Start_to_Finish-ScalarWave.ipynb)).

As detailed above, the suffix $\text{_dDD00}$ tells NRPy+ to construct the second finite difference derivative of gridfunction $\texttt{phi}$ with respect to coordinate $xx0$ (in this case $xx0$ is simply the Cartesian coordinate $x$). Here is the NRPy+ implementation:

```python
import sympy as sp               # SymPy, Python's core symbolic algebra package on which NRPy+ depends
from outputC import lhrh         # NRPy+: Core C code output module
import NRPy_param_funcs as par   # NRPy+: parameter interface
import grid as gri               # NRPy+: Functions having to do with numerical grids
import indexedexp as ixp         # NRPy+: Symbolic indexed expression (e.g., tensors, vectors, etc.) support
import finite_difference as fin  # NRPy+: Finite difference C code generation module

# Set the spatial dimension to 1
par.set_paramsvals_value("grid::DIM = 1")

# Register the input gridfunction "phi" and the gridfunction to which data are output, "output":
phi, output = gri.register_gridfunctions("AUX",["phi","output"])

# Declare phi_dDD as a rank-2 indexed expression: phi_dDD[i][j] = \partial_i \partial_j phi
phi_dDD = ixp.declarerank2("phi_dDD","nosym")

# Set output to \partial_0^2 phi
output = phi_dDD[0][0]

# Output to the screen the core C code for evaluating the finite difference derivative
fin.FD_outputC("stdout",lhrh(lhs=gri.gfaccess("out_gf","output"),rhs=output))
```

Some important points about the above code:
* The gridfunction PHIGF samples some function $\phi(x)$ at discrete uniform points in $x$, labeled $x_i$ at all points $i\in [0,N]$, so that
$$\phi(x_i) = \phi_{i}=\text{in_gfs[IDX2(PHIGF, i)]}.$$
* For a *uniformly* sampled function with constant grid spacing (sample rate) $\Delta x$, $x_i$ is defined as $x_i = x_0 + i \Delta x$.
* The variable $\texttt{invdx0}$ must be defined by the user in terms of the uniform gridspacing $\Delta x$ as $\texttt{invdx0} = \frac{1}{\Delta x}$.
     * *Aside*: Why do we choose to multiply by $1/\Delta x$ instead of dividing the expression by $\Delta x$, which would seem much more straightforward?
         * *Answer*: as discussed in the [first part of the tutorial](Tutorial-Coutput__Parameter_Interface.ipynb), division of floating-point numbers on modern CPUs is far more expensive than multiplication, usually by a factor of ~3 or more.

# Step 2: Output this notebook to $\LaTeX$-formatted PDF file \[Back to [top](#toc)\]
$$\label{latex_pdf_output}$$

The following code cell converts this Jupyter notebook into a proper, clickable $\LaTeX$-formatted PDF file. After the cell is successfully run, the generated PDF may be found in the root NRPy+ tutorial directory, with filename
[Tutorial-Finite_Difference_Derivatives.pdf](Tutorial-Finite_Difference_Derivatives.pdf) (Note that clicking on this link may not work; you may need to open the PDF file through another means.)

```python
import cmdline_helper as cmd    # NRPy+: Multi-platform Python command-line interface
cmd.output_Jupyter_notebook_to_LaTeXed_PDF("Tutorial-Finite_Difference_Derivatives")
```


# Customer Segmentation: Estimate Individualized Responses to Incentives

Nowadays, business decision makers rely on estimating the causal effect of interventions to answer what-if questions about shifts in strategy, such as promoting specific product with discount, adding new features to a website or increasing investment from a sales team. However, rather than learning whether to take action for a specific intervention for all users, people are increasingly interested in understanding the different responses from different users to the two alternatives. Identifying the characteristics of users having the strongest response for the intervention could help make rules to segment the future users into different groups. This can help optimize the policy to use the least resources and get the most profit.

In this case study, we will use a personalized pricing example to explain how the [EconML](https://aka.ms/econml) library could fit into this problem and provide robust and reliable causal solutions.

### Summary

1. [Background](#background)
2. [Data](#data)
3. [Get Causal Effects with EconML](#estimate)
4. [Understand Treatment Effects with EconML](#interpret)
5. [Make Policy Decisions with EconML](#policy)
6. [Conclusions](#conclusion)

# Background

The global online media market is growing fast over the years. Media companies are always interested in attracting more users into the market and encouraging them to buy more songs or become members. In this example, we'll consider a scenario where one experiment a media company is running is to give small discount (10%, 20% or 0) to their current users based on their income level in order to boost the likelihood of their purchase. The goal is to understand the **heterogeneous price elasticity of demand** for people with different income level, learning which users would respond most strongly to a small discount. Furthermore, their end goal is to make sure that despite decreasing the price for some consumers, the demand is raised enough to boost the overall revenue.

EconML’s `DMLCateEstimator` based estimators can be used to take the discount variation in existing data, along with a rich set of user features, to estimate heterogeneous price sensitivities that vary with multiple customer features. Then, the `SingleTreeCateInterpreter` provides a presentation-ready summary of the key features that explain the biggest differences in responsiveness to a discount, and the `SingleTreePolicyInterpreter` recommends a policy on who should receive a discount in order to increase revenue (not only demand), which could help the company to set an optimal price for those users in the future.

```python
# Some imports to get us started
# Utilities
import os
import urllib.request
import numpy as np
import pandas as pd

# Generic ML imports
from sklearn.preprocessing import PolynomialFeatures
from sklearn.ensemble import GradientBoostingRegressor

# EconML imports
from econml.dml import LinearDMLCateEstimator, ForestDMLCateEstimator
from econml.cate_interpreter import SingleTreeCateInterpreter, SingleTreePolicyInterpreter

import matplotlib.pyplot as plt

%matplotlib inline
```

# Data

The dataset* has ~10,000 observations and includes 9 continuous and categorical variables that represent user's characteristics and online behaviour history such as age, log income, previous purchase, previous online time per week, etc.

We define the following variables:

Feature Name|Type|Details
:--- |:---|:---
**account_age** |W| user's account age
**age** |W|user's age
**avg_hours** |W| the average hours user was online per week in the past
**days_visited** |W| the average number of days user visited the website per week in the past
**friend_count** |W| number of friends user connected in the account
**has_membership** |W| whether the user had membership
**is_US** |W| whether the user accesses the website from the US
**songs_purchased** |W| the average songs user purchased per week in the past
**income** |X| user's income
**price** |T| the price user was exposed during the discount season (baseline price * small discount)
**demand** |Y| songs user purchased during the discount season

**To protect the privacy of the company, we use the simulated data as an example here. The data is synthetically generated and the feature distributions don't correspond to real distributions. However, the feature names have preserved their names and meaning.*

The treatment and outcome are generated using the following functions:
$$
T =
\begin{cases}
  1 & \text{with } p=0.2,  \\
  0.9 & \text{with }p=0.3, & \text{if income}<1 \\
  0.8 & \text{with }p=0.5, \\
  \\
    1 & \text{with }p=0.7, \\
  0.9 & \text{with }p=0.2, & \text{if income}\ge1 \\
  0.8 & \text{with }p=0.1, \\
\end{cases}
$$

\begin{align}
\gamma(X) & = -3 - 14 \cdot \{\text{income}<1\} \\
\beta(X,W) & = 20 + 0.5 \cdot \text{avg_hours} + 5 \cdot \{\text{days_visited}>4\} \\
Y &= \gamma(X) \cdot T + \beta(X,W)
\end{align}

```python
# Import the sample pricing data
file_url = "https://msalicedatapublic.blob.core.windows.net/datasets/Pricing/pricing_sample.csv"
train_data = pd.read_csv(file_url)
```

```python
# Data sample
train_data.head()
```

      account_age
      age
      avg_hours
      days_visited
      friends_count
      has_membership
      is_US
      songs_purchased
      income
      price
      demand

      0
      3
      53
      1.834234
      2
      8
      1
      1
      4.903237
      0.960863
      1.0
      3.917117

      1
      5
      54
      7.171411
      7
      9
      0
      1
      3.330161
      0.732487
      1.0
      11.585706

      2
      3
      33
      5.351920
      6
      9
      0
      1
      3.036203
      1.130937
      1.0
      24.675960

      3
      2
      34
      6.723551
      0
      8
      0
      1
      7.911926
      0.929197
      1.0
      6.361776

      4
      4
      30
      2.448247
      5
      8
      1
      0
      7.148967
      0.533527
      0.8
      12.624123

```python
# Define estimator inputs
Y = train_data["demand"]  # outcome of interest
T = train_data["price"]  # intervention, or treatment
X = train_data[["income"]]  # features
W = train_data.drop(columns=["demand", "price", "income"])  # confounders
```

```python
# Get test data
X_test = np.linspace(0, 5, 100).reshape(-1, 1)
X_test_data = pd.DataFrame(X_test, columns=["income"])
```

# Get Causal Effects with EconML
To learn the price elasticity on demand as a function of income, we fit the model as follows:

\begin{align}
log(Y) & = \theta(X) \cdot log(T) + f(X,W) + \epsilon \\
log(T) & = g(X,W) + \eta
\end{align}

where $\epsilon, \eta$ are uncorrelated error terms.

The models we fit here aren't an exact match for the data generation function above, but if they are a good approximation, they will allow us to create a good discount policy.  Although the model is misspecified, we hope to see that our `DMLCateEstimator` based estimators can still capture the right trend of $\theta(X)$ and that the recommended policy beats other baseline policies (such as always giving a discount) on revenue.  Because of the mismatch between the data generating process and the model we're fitting, there isn't a single true $\theta(X)$ (the true elasticity varies with not only X but also T and W), but given how we generate the data above, we can still calculate the range of true $\theta(X)$ to compare against.

```python
# Define underlying treatment effect function given DGP
def gamma_fn(X):
    return -3 - 14 * (X["income"] < 1)

def beta_fn(X):
    return 20 + 0.5 * (X["avg_hours"]) + 5 * (X["days_visited"] > 4)

def demand_fn(data, T):
    Y = gamma_fn(data) * T + beta_fn(data)
    return Y

def true_te(x, n, stats):
    if x < 1:
        subdata = train_data[train_data["income"] < 1].sample(n=n, replace=True)
    else:
        subdata = train_data[train_data["income"] >= 1].sample(n=n, replace=True)
    te_array = subdata["price"] * gamma_fn(subdata) / (subdata["demand"])
    if stats == "mean":
        return np.mean(te_array)
    elif stats == "median":
        return np.median(te_array)
    elif isinstance(stats, int):
        return np.percentile(te_array, stats)
```

```python
# Get the estimate and range of true treatment effect
truth_te_estimate = np.apply_along_axis(true_te, 1, X_test, 1000, "mean")  # estimate
truth_te_upper = np.apply_along_axis(true_te, 1, X_test, 1000, 95)  # upper level
truth_te_lower = np.apply_along_axis(true_te, 1, X_test, 1000, 5)  # lower level
```

## Parametric heterogeneity
First of all, we can try to learn a **linear projection of the treatment effect** assuming a polynomial form of $\theta(X)$. We use the `LinearDMLCateEstimator` estimator. Since we don't have any priors on these models, we use a generic gradient boosting tree estimators to learn the expected price and demand from the data.

```python
# Get log_T and log_Y
log_T = np.log(T)
log_Y = np.log(Y)
```

```python
# Train EconML model
est = LinearDMLCateEstimator(
    model_y=GradientBoostingRegressor(),
    model_t=GradientBoostingRegressor(),
    featurizer=PolynomialFeatures(degree=2, include_bias=False),
)
est.fit(log_Y, log_T, X, W, inference="statsmodels")
# Get treatment effect and its confidence interval
te_pred = est.effect(X_test)
te_pred_interval = est.effect_interval(X_test)
```

```python
# Compare the estimate and the truth
plt.figure(figsize=(10, 6))
plt.plot(X_test.flatten(), te_pred, label="Sales Elasticity Prediction")
plt.plot(X_test.flatten(), truth_te_estimate, "--", label="True Elasticity")
plt.fill_between(
    X_test.flatten(),
    te_pred_interval[0],
    te_pred_interval[1],
    alpha=0.2,
    label="90% Confidence Interval",
)
plt.fill_between(
    X_test.flatten(),
    truth_te_lower,
    truth_te_upper,
    alpha=0.2,
    label="True Elasticity Range",
)
plt.xlabel("Income")
plt.ylabel("Songs Sales Elasticity")
plt.title("Songs Sales Elasticity vs Income")
plt.legend(loc="lower right")
```

From the plot above, it's clear to see that the true treatment effect is a **nonlinear** function of income, with elasticity around -1.75 when income is smaller than 1 and a small negative value when income is larger than 1. The model fits a quadratic treatment effect, which is not a great fit. But it still captures the overall trend: the elasticity is negative and people are less sensitive to the price change if they have higher income.

```python
# Get the final coefficient and intercept summary
est.summary(feat_name=X.columns)
```

Coefficient Results

      	     point_estimate	 stderr	  zstat	  pvalue	 ci_lower	 ci_upper

  income	        2.451	      0.065	 37.659	    0.0	    2.344	    2.558

  income^2	     -0.443	      0.022	 -20.517	   0.0	   -0.479	   -0.408

Intercept Results

      	      point_estimate	 stderr	  zstat	  pvalue	 ci_lower	 ci_upper

  intercept	      -3.04	      0.042	 -72.165	   0.0	   -3.109	    -2.97

`LinearDMLCateEstimator` estimator can also return the summary of the coefficients and intercept for the final model, including point estimates, p-values and confidence intervals. From the table above, we notice that $income$ has positive effect and ${income}^2$ has negative effect, and both of them are statistically significant.

## Nonparametric Heterogeneity
Since we already know the true treatment effect function is nonlinear, let us fit another model using `ForestDMLCateEstimator`, which assumes a fully **nonparametric estimation of the treatment effect**.

```python
# Train EconML model
est = ForestDMLCateEstimator(
    model_y=GradientBoostingRegressor(), model_t=GradientBoostingRegressor()
)
est.fit(log_Y, log_T, X, W, inference="blb")
# Get treatment effect and its confidence interval
te_pred = est.effect(X_test)
te_pred_interval = est.effect_interval(X_test)
```

```python
# Compare the estimate and the truth
plt.figure(figsize=(10, 6))
plt.plot(X_test.flatten(), te_pred, label="Sales Elasticity Prediction")
plt.plot(X_test.flatten(), truth_te_estimate, "--", label="True Elasticity")
plt.fill_between(
    X_test.flatten(),
    te_pred_interval[0],
    te_pred_interval[1],
    alpha=0.2,
    label="90% Confidence Interval",
)
plt.fill_between(
    X_test.flatten(),
    truth_te_lower,
    truth_te_upper,
    alpha=0.2,
    label="True Elasticity Range",
)
plt.xlabel("Income")
plt.ylabel("Songs Sales Elasticity")
plt.title("Songs Sales Elasticity vs Income")
plt.legend(loc="lower right")
```

We notice that this model fits much better than the `LinearDMLCateEstimator`, the 90% confidence interval correctly covers the true treatment effect estimate and captures the variation when income is around 1. Overall, the model shows that people with low income are much more sensitive to the price changes than higher income people.

# Understand Treatment Effects with EconML
EconML includes interpretability tools to better understand treatment effects. Treatment effects can be complex, but oftentimes we are interested in simple rules that can differentiate between users who respond positively, users who remain neutral and users who respond negatively to the proposed changes.

The EconML `SingleTreeCateInterpreter` provides interperetability by training a single decision tree on the treatment effects outputted by the any of the EconML estimators. In the figure below we can see in dark red users respond strongly to the discount and the in white users respond lightly to the discount.

```python
intrp = SingleTreeCateInterpreter(include_model_uncertainty=True, max_depth=2, min_samples_leaf=10)
intrp.interpret(est, X_test)
plt.figure(figsize=(25, 5))
intrp.plot(feature_names=X.columns, fontsize=12)
```

# Make Policy Decision with EconML
We want to make policy decisions to maximum the **revenue** instead of the demand. In this scenario,

\begin{align}
Rev & = Y \cdot T \\
    & = \exp^{log(Y)} \cdot T\\
    & = \exp^{(\theta(X) \cdot log(T) + f(X,W) + \epsilon)} \cdot T \\
    & = \exp^{(f(X,W) + \epsilon)} \cdot T^{(\theta(X)+1)}
\end{align}

With the decrease of price, revenue will increase only if $\theta(X)+1<0$. Thus, we set `sample_treatment_cast=-1` here to learn **what kinds of customers we should give a small discount to maximum the revenue**.

The EconML library includes policy interpretability tools such as `SingleTreePolicyInterpreter` that take in a treatment cost and the treatment effects to learn simple rules about which customers to target profitably. In the figure below we can see the model recommends to give discount for people with income less than $0.985$ and give original price for the others.

```python
intrp = SingleTreePolicyInterpreter(risk_level=0.05, max_depth=2, min_samples_leaf=1, min_impurity_decrease=0.001)
intrp.interpret(est, X_test, sample_treatment_costs=-1, treatment_names=["Discount", "No-Discount"])
plt.figure(figsize=(25, 5))
intrp.plot(feature_names=X.columns, fontsize=12)
```

Now, let us compare our policy with other baseline policies! Our model says which customers to give a small discount to, and for this experiment, we will set a discount level of 10% for those users. Because the model is misspecified we would not expect good results with large discounts. Here, because we know the ground truth, we can evaluate the value of this policy.

```python
# define function to compute revenue
def revenue_fn(data, discount_level1, discount_level2, baseline_T, policy):
    policy_price = baseline_T * (1 - discount_level1) * policy + baseline_T * (1 - discount_level2) * (1 - policy)
    demand = demand_fn(data, policy_price)
    rev = demand * policy_price
    return rev
```

```python
policy_dic = {}
# our policy above
policy = intrp.treat(X)
policy_dic["Our Policy"] = np.mean(revenue_fn(train_data, 0, 0.1, 1, policy))

## previous strategy
policy_dic["Previous Strategy"] = np.mean(train_data["price"] * train_data["demand"])

## give everyone discount
policy_dic["Give Everyone Discount"] = np.mean(revenue_fn(train_data, 0.1, 0, 1, np.ones(len(X))))

## don't give discount
policy_dic["Give No One Discount"] = np.mean(revenue_fn(train_data, 0, 0.1, 1, np.ones(len(X))))

## follow our policy, but give -10% discount for the group doesn't recommend to give discount
policy_dic["Our Policy + Give Negative Discount for No-Discount Group"] = np.mean(revenue_fn(train_data, -0.1, 0.1, 1, policy))

## give everyone -10% discount
policy_dic["Give Everyone Negative Discount"] = np.mean(revenue_fn(train_data, -0.1, 0, 1, np.ones(len(X))))
```

```python
# get policy summary table
res = pd.DataFrame.from_dict(policy_dic, orient="index", columns=["Revenue"])
res["Rank"] = res["Revenue"].rank(ascending=False)
res
```

      Revenue
      Rank

      Our Policy
      14.686241
      2.0

      Previous Strategy
      14.349342
      4.0

      Give Everyone Discount
      13.774469
      6.0

      Give No One Discount
      14.294606
      5.0

      Our Policy + Give Negative Discount for No-Discount Group
      15.564411
      1.0

      Give Everyone Negative Discount
      14.612670
      3.0

**We beat the baseline policies!** Our policy gets the highest revenue except for the one raising the price for the No-Discount group. That means our currently baseline price is low, but the way we segment the user does help increase the revenue!

# Conclusions

In this notebook, we have demonstrated the power of using EconML to:

* Estimate the treatment effect correctly even the model is misspecified
* Interpret the resulting individual-level treatment effects
* Make the policy decision beats the previous and baseline policies

To learn more about what EconML can do for you, visit our [website](https://aka.ms/econml), our [GitHub page](https://github.com/microsoft/EconML) or our [documentation](https://econml.azurewebsites.net/).

notebooks/CustomerScenarios/Case Study - Customer Segmentation at An Online Media Company.ipynb