diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 785077a..99b9764 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,6 +35,11 @@ jobs: - name: Upgrade pip run: python -m pip install --upgrade pip + - name: Install PyTorch + run: > + python -m pip install torch + --index-url https://download.pytorch.org/whl/cpu + - name: Install CalculiX run: | sudo apt-get update diff --git a/.gitignore b/.gitignore index 97d2161..796d288 100644 --- a/.gitignore +++ b/.gitignore @@ -225,6 +225,9 @@ __pycache__/ # Virtual environment .venv/ +# ML model checkpoints +models/checkpoints/ + # Testing .pytest_cache/ .coverage diff --git a/README.md b/README.md index b6199a1..3cfbad5 100644 --- a/README.md +++ b/README.md @@ -136,4 +136,31 @@ The initial implementation does not yet include: - notch effects - multiaxial fatigue - low-cycle fatigue -- crack-growth modelling \ No newline at end of file +- crack-growth modelling + +## PyTorch Structural Surrogate + +BodySimPy includes a feed-forward neural-network surrogate trained on a six-dimensional CalculiX design dataset. + +### Inputs + +- wall thickness +- section height +- section width +- Young's modulus +- material density +- applied tip force + +### Predicted responses + +- maximum axial stress +- tip displacement +- mode-1 natural frequency + +The training dataset is generated using Latin Hypercube sampling across a bounded engineering design space and evaluated using automated static and modal CalculiX analyses. + +The dataset is separated into training, validation and held-out test subsets. Input and target normalization statistics are fitted exclusively on the training subset. + +Training includes validation monitoring, early stopping and best-model checkpointing. + +The surrogate is intended for interpolation within the sampled design space and does not replace finite-element validation outside that domain. \ No newline at end of file diff --git a/data/surrogate/fea_surrogate_dataset.csv b/data/surrogate/fea_surrogate_dataset.csv new file mode 100644 index 0000000..3a4ee45 --- /dev/null +++ b/data/surrogate/fea_surrogate_dataset.csv @@ -0,0 +1,501 @@ +sample,thickness_m,height_m,width_m,youngs_modulus_pa,density_kg_m3,tip_force_n,max_stress_pa,tip_deflection_m,mode_1_frequency_hz +1,0.001348452087902888,0.04458420037616893,0.09225131216640708,218511184237.96585,8055.905822652112,713.229253178036,108294000.0,0.008143366,56.005 +2,0.001696477720596019,0.048423701685010034,0.07774975090938596,196898472984.34732,7794.6292019757675,794.8878820133816,102292000.0,0.00789147,56.87558 +3,0.0017907122697598387,0.03812638058192225,0.08452452686409381,207974093289.47507,7868.445415212984,1117.523419292675,171760000.0,0.01585583,46.84363 +4,0.0011863447376560148,0.03608126008163161,0.08065935298079317,202295183182.2309,7974.029301975605,1004.9282546544133,248973000.0,0.02497453,44.07869 +5,0.0013884432330058525,0.03786099300651733,0.08404266231970184,199516933736.3949,7863.845710507932,1177.980341256109,230216000.0,0.02231018,46.04687 +6,0.0013725104756881846,0.03524116964963237,0.07725393397134894,204464067820.57755,8095.530444188724,1100.572634369099,255268000.0,0.02595197,42.62491 +7,0.001145740156989329,0.03572687462265587,0.06406184725207592,203533113020.37222,7611.562848081127,853.8007861647307,269621000.0,0.02724539,43.87272 +8,0.0014865994697959954,0.04444475480091025,0.08081341921588839,194633666494.97522,7993.612521620969,858.0540062752837,133973000.0,0.011374,52.04468 +9,0.00101663500899205,0.042254968910590066,0.068144007343802,198889484641.11743,7851.213075622498,1102.4021789720896,300353000.0,0.02628742,50.2974 +10,0.0018945896692427475,0.04057589375488321,0.06468328673795694,193950188116.2833,7975.860203001872,1046.662563911757,178425000.0,0.01667675,46.22168 +11,0.001884663194076419,0.04584304053657885,0.08219478111148151,209826450079.9809,8042.3652816799995,1289.7357047192104,154302000.0,0.01178213,55.06158 +12,0.001536881585678509,0.04581305779646975,0.07783753457323457,202499429782.75372,7995.78541532718,1106.3097656275304,166658000.0,0.01320517,54.3073 +13,0.0019582931938534635,0.04390757817850885,0.08463533578066487,209020303127.55847,7733.706406242233,1188.8057001823277,141508000.0,0.01131217,53.9044 +14,0.0014788859356953175,0.038739779664472174,0.09338685491677381,213701552919.6992,8074.185979615334,879.7996324961108,144090000.0,0.01270998,48.4186 +15,0.0014639545758537323,0.03422875827701208,0.06954221125195229,203337668593.88242,7873.838728220966,1252.5987462698758,311583000.0,0.03284355,41.40182 +16,0.0016256953757945738,0.048930932466497205,0.09236430749795407,206983328514.17325,8030.698487910852,905.6436608882573,103329000.0,0.00748595,59.16491 +17,0.0018492763747788932,0.03721684460290462,0.09223055952783037,201972667163.48154,8029.091419309292,965.5603514394271,137729000.0,0.0133726,44.95145 +18,0.001665468260077081,0.04812910965041549,0.08321769992768274,203109817686.75888,7676.550638497856,1040.4733101257857,129528000.0,0.009737118,58.3487 +19,0.001585807218075693,0.049607506313724424,0.06092353789681331,217425834564.46432,7647.694043375849,1090.5049365172697,195999000.0,0.01209061,60.08819 +20,0.0019836464544341218,0.03787316188577267,0.06977931843761317,193729637593.08344,7909.567906960225,1013.647229391157,169904000.0,0.01700712,43.74022 +21,0.0019348318040621746,0.04216460552234403,0.0942332444543088,210830893481.84805,7616.9583858261385,848.2072110169065,97771300.0,0.008043222,53.09488 +22,0.0012473402775753345,0.03555879712920082,0.0641517277625822,213268864879.94757,7774.829407031463,730.0898558579479,214402000.0,0.02077267,44.12479 +23,0.001964837877720599,0.04923151268703675,0.0893927267606815,203648403729.02792,7966.041440786758,904.6212358756685,88793400.0,0.006502684,58.65858 +24,0.0012804345295454995,0.03343702172000279,0.09364106733353295,191085650510.39517,8062.062173545025,855.3139263371487,190176000.0,0.02165979,40.07667 +25,0.0014570530211978861,0.04592238887612869,0.0875734744802126,203823552931.827,7862.5610885396945,985.5740655041436,140579000.0,0.01102018,55.8459 +26,0.0018443474161516113,0.04053573821221377,0.08206878007288011,214811217469.95227,7907.891424258864,1255.9933118883523,178835000.0,0.01505834,50.10898 +27,0.001277437532432322,0.04324426078515109,0.09738184043085706,224316194675.56577,7927.892259054044,996.5007857858349,157497000.0,0.01187027,55.98148 +28,0.0015155395720182104,0.032826653147977655,0.09939561180244869,220284035440.12964,7663.170210256868,1261.830098233523,234129000.0,0.02348803,43.2723 +29,0.0012713657222143547,0.04089369562177749,0.0889367265729488,200393946000.9538,7898.744034909432,700.0767477159412,128567000.0,0.01148534,49.8972 +30,0.001077670784364836,0.03631838321770188,0.09284519223519977,199310533710.51608,8077.108322733745,1164.7016703766517,276687000.0,0.02788085,44.43517 +31,0.0014462184150182428,0.04586383592097088,0.08259849133116909,219307884966.37186,7725.227987567889,982.4060064841988,148535000.0,0.01084629,58.03304 +32,0.0012612526845422526,0.03696459919994979,0.06762025683109207,217981627763.8854,7858.063186849467,1280.5108353099931,339243000.0,0.03092789,46.23214 +33,0.001191754484135177,0.04748607994380351,0.08054773725467004,223307451218.42896,8086.400617208479,916.1505255509951,162137000.0,0.01123982,59.24665 +34,0.0013836071306685708,0.0393328283477756,0.0760178076129407,192031972150.17572,7703.4992588137975,756.2273229969385,154343000.0,0.01499711,46.84001 +35,0.0011819721274245836,0.041531732382920045,0.09454945422257707,198492563892.8471,7729.878167495372,1218.9924040820551,222919000.0,0.01977011,51.33286 +36,0.001956611475128714,0.037343079802068045,0.0901440179478672,207303711283.16724,7651.284592870385,1030.3132191953014,142191000.0,0.0134156,46.5728 +37,0.0014817378844968854,0.03843954486306859,0.06672579499591948,208172169526.43225,8064.699051308219,931.0136991455781,205546000.0,0.01887922,45.91477 +38,0.0012186742715744729,0.04924559756274631,0.08725708430184945,213525263409.94815,7930.975140508614,788.1337623492078,122157000.0,0.008534013,60.95769 +39,0.0011827320497766378,0.03624418769346498,0.06390877283223488,203270661997.64786,7899.0337680878565,1269.2847489361188,383328000.0,0.03823809,43.55952 +40,0.0016141339535567997,0.038559043007037934,0.09772260947187558,194915066558.55234,8003.982163216023,774.2690272038811,113321000.0,0.01099242,46.26854 +41,0.0015643411427702348,0.0440673145848203,0.08734138874064269,206062846128.65808,7882.3939841793,743.3587132554687,104776000.0,0.008460986,53.83766 +42,0.0013587937856853226,0.04314514742350615,0.08917006527652743,199700188253.95386,7845.34806897442,1082.9590112418973,174947000.0,0.01487771,52.43997 +43,0.0019050922062358476,0.03245907777573375,0.0697410670109602,216267779000.38297,7803.183431236576,1299.873666304175,274448000.0,0.02865977,40.25603 +44,0.0013878668822860896,0.04555060038810477,0.06998830614046458,223822273506.66806,8053.689665326076,729.8273536804346,132889000.0,0.00959169,56.1129 +45,0.001890158059055025,0.044954040857981534,0.09669722239341297,217009247062.33655,8096.884509936334,943.5746223803962,100299000.0,0.007523472,55.69606 +46,0.0018838892091816715,0.03836571290704631,0.09983572944538983,220688619932.87222,7624.319285473201,808.7276434518016,101970000.0,0.008771493,49.94958 +47,0.001551364017806096,0.037633837055467204,0.08992999960462122,217660420786.12244,7661.9565249379875,1297.3822019084841,217923000.0,0.0194375,48.54919 +48,0.001287526510257791,0.04603102204727004,0.06707430138786058,190530861630.20078,8020.950745880073,1126.7516630338484,224748000.0,0.01886597,52.2362 +49,0.0016909524941025648,0.03970033981149547,0.06593332331569692,190906362669.34744,8000.075158130982,1205.0810642301042,229538000.0,0.02226882,45.19085 +50,0.0018343128500968044,0.03394750448681861,0.08304163434555308,194003858188.38086,7709.220522459229,1240.4290200688054,219118000.0,0.02431215,40.79314 +51,0.0010784420072908437,0.04099515612049713,0.06027711455712419,207744004399.08667,7743.142427855877,1286.2446407612879,381397000.0,0.03296323,49.60012 +52,0.0013132298210077992,0.039284975722235466,0.06101868293459993,223730216211.23322,7893.522122725976,1034.2997327576861,267248000.0,0.0223709,48.82061 +53,0.0017755348601189423,0.03771076957483586,0.0756506886040139,215387075310.49112,7983.620535920273,954.7771079854529,165099000.0,0.0149099,46.35244 +54,0.0012894062470508575,0.043845841114638254,0.06368669215843695,202076336270.01898,7781.6716387949955,938.157478252204,207300000.0,0.01722404,51.94343 +55,0.0014023028790224527,0.03737650685573984,0.06665564865376424,202632709440.6754,7691.3670823707325,807.6542132629811,194167000.0,0.01883775,45.2827 +56,0.0010885302136753794,0.04344071343464748,0.08802441614969453,192739749665.2172,8082.867897162699,918.8627443113497,182799000.0,0.0160043,51.32232 +57,0.0018478098085035403,0.04763387423738223,0.07559324054249411,195324484212.41095,7749.862592069962,1169.249343704909,145682000.0,0.0115185,55.61009 +58,0.0018003982316478298,0.042526627447839215,0.08465739007162965,217594341961.27118,7905.053972937183,1065.695939975393,142150000.0,0.01126476,52.98613 +59,0.0016508198482092834,0.03491257822888768,0.07611070674399807,207418009608.7135,7683.435049388528,707.7130833831594,142822000.0,0.01444973,43.27674 +60,0.0012690680296940375,0.03392518525608153,0.08281888612879948,212904052869.84415,7788.507846784424,819.2804878701729,200990000.0,0.02033826,43.18549 +61,0.0018181375275286198,0.03901568959081396,0.087910631714728,210153860369.16626,7782.342136714959,1190.2976700390516,169512000.0,0.01512452,48.54178 +62,0.0018104513571677162,0.03305583566912089,0.08133330897933375,206737114341.6847,8089.2374678529295,1245.6753580709078,234317000.0,0.02505715,40.02304 +63,0.001533271615964456,0.04149268016071265,0.06558739106811,209309655163.92206,7818.063872536624,1198.6745151553614,236665000.0,0.02004634,50.02722 +64,0.0015552333603544451,0.047417731314487614,0.09747576055477977,190354470200.9213,7925.219698506091,1087.0247565230504,128152000.0,0.01039898,55.8595 +65,0.0016672472810530382,0.048524481264382725,0.07266257918111971,201063416373.79974,7982.881521422783,1076.979359584971,148613000.0,0.01121207,56.50439 +66,0.0015087258522320176,0.03833161081957994,0.06219293936000246,211861973254.44263,7682.987697314112,1239.45481844579,286897000.0,0.02598205,46.94104 +67,0.0010943492009776073,0.04242936707508761,0.06900331615345612,196129038011.10526,7627.698072608558,1273.6978697253812,319005000.0,0.02819403,50.8425 +68,0.001098437411929137,0.04211201881720414,0.071840237227373,206318455998.29846,7954.716091562136,732.5957241044068,178383000.0,0.01509239,50.9313 +69,0.0011277871609362573,0.04708403222970293,0.06802674522111148,220824491248.9109,7640.9095592730155,769.7235599213241,166825000.0,0.01181374,59.10413 +70,0.001571942000993591,0.046895330189910524,0.07302855824998666,201775626244.15155,7932.801795772787,776.907234127443,117528000.0,0.009138944,55.17533 +71,0.001364687461921345,0.04629069814242219,0.0725595656132697,217576383945.809,8050.394074822352,747.0382218269687,131375000.0,0.009596979,56.41058 +72,0.0013395228943587454,0.03751342128165477,0.09031542144478774,201563932530.2451,8091.072204569828,888.6734699210747,170410000.0,0.01646369,45.59591 +73,0.0016186037584320428,0.037045832712128406,0.09391678789603625,222255875252.24966,8088.8750763206945,1221.3945628117685,193891000.0,0.01717535,47.13879 +74,0.0015765096237433424,0.04943731957359525,0.09961190951201905,223099135090.5291,7964.265378908065,964.9683502109574,104822000.0,0.006962929,62.81112 +75,0.001999458482497364,0.04817444343089787,0.0626415836172081,219497191947.5816,7900.945499685009,1193.6604292360869,159874000.0,0.0111395,57.53912 +76,0.0010859152988968398,0.038196170754390794,0.08514323373719196,219107881050.75043,7717.908278994176,910.9797601253435,218138000.0,0.01907864,49.55567 +77,0.0017480399456408916,0.03799549181687149,0.08137727352413268,220195451416.05246,7646.427586850042,908.625843694402,148075000.0,0.01296576,48.61301 +78,0.0017961079510928838,0.03447315166522947,0.0723537586227354,201151015683.87875,7690.350766844755,889.6712867083654,177220000.0,0.01873818,41.70928 +79,0.0015997031220201599,0.04422169930141591,0.09924767724874074,219576808188.93622,7737.880782473802,853.4390864681527,105123000.0,0.007913983,56.93701 +80,0.0011474438489084374,0.03227703063426461,0.0850857677429055,220407230871.70743,8068.365370882581,1132.7058269240974,315737000.0,0.03238096,41.43197 +81,0.0015591824337811426,0.048804165373038916,0.08243293550012729,190467807136.2078,7872.963940165722,841.0979199470117,110154000.0,0.008711099,56.57787 +82,0.0012330517346498197,0.047075878656744646,0.06491420336535023,212850395966.96976,7886.29799781889,1064.0224618213585,220634000.0,0.01621748,56.67476 +83,0.001332695133886958,0.04120461629053434,0.06769700542224112,213761559893.148,7675.5685818048205,1163.6487850228707,256140000.0,0.02138535,51.11914 +84,0.0017172786853310996,0.04544554138719416,0.06434106354492142,202957951797.92935,7984.078942802705,929.7956404897802,150226000.0,0.01199255,52.66065 +85,0.001458959449770519,0.040143204665212505,0.09325484394466046,213881383234.63583,7723.505858353483,1247.0609719307977,198253000.0,0.0168757,51.23284 +86,0.0019258558817058541,0.04161768824440258,0.09559555456664745,201390357199.47064,7674.665869959498,759.79240666577,88261800.0,0.007695855,51.14129 +87,0.001309372213260396,0.043673263067597896,0.07399882537251519,217172097860.28354,7628.143451976425,1227.55328611217,237025000.0,0.0183713,55.07916 +88,0.0013916927742018788,0.04958236974531398,0.09487042619553082,197114455644.72452,7724.041570819099,835.6054426328997,105761000.0,0.007937739,60.0357 +89,0.0014304557902172909,0.03234880538564285,0.09082498679606672,205980621554.4161,7920.612158304836,906.8309336384966,195210000.0,0.02132564,40.3866 +90,0.0016479785447100486,0.04160447392429032,0.06715799293576262,195588537408.7019,7645.834093132124,836.9964348513397,151359000.0,0.01368052,49.01709 +91,0.0014840217339946327,0.039791985100590975,0.08145287442152946,217090677483.49716,7615.858404111431,970.6621052640947,172721000.0,0.01465851,50.85731 +92,0.0010252148545683163,0.03509311822578874,0.08921957358617642,192139635468.08743,7870.530673065809,1278.2191924314243,342983000.0,0.0371235,42.70743 +93,0.0017776381466955383,0.038807344269602376,0.08335642807288206,196213700340.05396,8038.707940669686,1275.4117629491182,195361000.0,0.01879191,45.73113 +94,0.0011588271096539704,0.04245824156086938,0.09247327077323544,204682943278.2161,8070.156411615238,869.6348935594258,160797000.0,0.01354181,51.99053 +95,0.0014907017018997316,0.04777987268280735,0.07529896775847683,204555932406.2795,8004.633391615067,1226.152567077686,185360000.0,0.01395305,56.54602 +96,0.0016113230870334054,0.032545598760561664,0.08076139419931302,197786196068.73785,7756.147984483119,923.7942502205158,197163000.0,0.02238252,39.59974 +97,0.0013681808955122573,0.04259886353814067,0.09833197805609331,199986158644.3086,7658.501183041469,1149.3623067507945,172204000.0,0.01476881,52.97057 +98,0.001361790040568208,0.03713365210975875,0.06856661298619586,210045841743.17447,7973.822493417563,820.7933732372685,199348000.0,0.01877233,45.18773 +99,0.0018596167939806747,0.04477910636725525,0.06960580316331967,210898625487.022,7708.692464927579,761.6818273701307,109397000.0,0.008522956,54.20973 +100,0.001211985371087414,0.049485991204162375,0.09490375732274409,216695636115.88516,8097.018194052457,1233.2555707484103,177883000.0,0.01216774,61.58928 +101,0.0016730449882527447,0.036832588427725375,0.07048773377024288,217249551283.7297,7631.0367887764505,843.0615937951142,168176000.0,0.0154292,46.36567 +102,0.001984266397123476,0.04851989225343739,0.07122140691976049,197599193791.03308,8099.446896197589,947.6150771086211,113824000.0,0.008740041,55.11961 +103,0.0012080603697665888,0.04510455518906847,0.060536941740360964,200658637255.17935,7613.250016462065,1195.1842085664243,282157000.0,0.02296308,53.47129 +104,0.0017077522604502717,0.039882525909429714,0.07145781254979766,195495721018.0728,7806.014459830505,988.6420318525677,173256000.0,0.0163252,46.87674 +105,0.0011480441660418523,0.04138118326392101,0.09273650542795521,219394062650.93817,7972.444538289862,810.6375282556171,155546000.0,0.01253204,52.907 +106,0.0018541505966655604,0.03351838700873777,0.0635170423418239,206843910836.10852,8011.951301527686,835.4688612506625,188075000.0,0.01992095,39.75445 +107,0.001968649388742665,0.04288232270493278,0.09353810234537988,193019418041.6361,7933.260568531524,933.038954089472,104399000.0,0.00922896,50.50543 +108,0.0017099020725803533,0.04833555673458412,0.09779024817812998,199879931228.67627,7955.99549987506,858.9824447894604,90494800.0,0.006862408,57.99086 +109,0.0016426454252012712,0.048234934137591376,0.06036789155539573,220877305558.30313,8043.328387191144,1038.1148099639672,188791000.0,0.01178718,57.44239 +110,0.001872294437740736,0.04753947086875726,0.07865173242630848,196769073944.4217,7792.409679749117,1223.9236719187027,146110000.0,0.0114848,55.77594 +111,0.0011974503498984898,0.035460083927761815,0.08270499250544484,224644063188.6135,7977.658073061454,1291.93883302283,317101000.0,0.02911953,45.78276 +112,0.0014523851367880475,0.0453920452386776,0.0653362910354391,190671444063.4208,8032.86317872197,813.3101536844524,151104000.0,0.01285387,51.21867 +113,0.0010340048399254743,0.03625876828363566,0.08667238297276694,200052534163.49548,7986.084701239714,1133.8037000846437,297724000.0,0.03000099,44.47878 +114,0.001685731266173374,0.04782684637532483,0.07743462666649217,202795861467.3618,7786.982331219,793.0560641001998,104605000.0,0.007932236,57.08203 +115,0.0013407309194700965,0.037747641471354364,0.07284636945827958,204142855350.90088,7970.248036878425,1236.950972035666,283430000.0,0.02699798,45.54985 +116,0.0014934309182497764,0.04867170811524417,0.08446119223064955,217067226008.86383,7853.090935185577,1025.7645495336578,137356000.0,0.009553524,60.56283 +117,0.001198515364101164,0.04605473318741011,0.07138772486327924,214059043169.59396,7722.758627938373,1044.0130091833976,211146000.0,0.01575988,56.96 +118,0.0010216891168718333,0.04665652468284953,0.07677734024534,222440695742.391,7840.208025574859,864.6594110687491,188005000.0,0.01332178,58.99988 +119,0.0016051232274828316,0.03291885473689088,0.0692004238600852,207662778176.61148,7614.1853615568825,832.1169352369369,201270000.0,0.02159107,40.81618 +120,0.0011084248203275414,0.04938133354358168,0.089075026341301,218762867408.98798,7894.347077873436,734.8535806861746,121922000.0,0.008288404,62.26866 +121,0.0010369745358713997,0.04134441299025676,0.08375713257988655,201412815783.43695,7650.89699731587,896.7507050880342,206721000.0,0.01820158,51.33057 +122,0.0016395078667769901,0.04052964090437894,0.07204359057260706,215513873853.912,7778.687430050972,872.7388474209101,154326000.0,0.01298133,50.17742 +123,0.0014157767992635016,0.040194157842392365,0.09439515772393388,216831126719.2073,7878.792979458475,927.6375141174597,149726000.0,0.01255155,51.24648 +124,0.0017992117484456438,0.046781002053209515,0.07455986714338601,224082155074.0301,7716.137235436259,783.9985203249857,103531000.0,0.007265009,58.6775 +125,0.0017809836652883742,0.033854377927765744,0.07669335246711916,193469807108.58975,7931.2194188624735,928.190555552542,181016000.0,0.02023451,39.82768 +126,0.0018994793029131695,0.038684390344125165,0.07414060137175774,203196409561.96143,8060.316741642341,1179.7260965494438,189596000.0,0.01770411,45.6568 +127,0.0016013385285158902,0.04103850615376679,0.06295611445681225,212367746127.63324,7728.288441958403,794.6194551861311,158395000.0,0.01337289,49.8604 +128,0.001161905251959989,0.049023024606621016,0.09408649414451756,214989351815.27252,7711.8116780251885,715.5624597845181,109396000.0,0.007613808,62.32751 +129,0.0014257787410648756,0.03924967462568138,0.07518066888730088,213480155156.46985,8022.409804617234,1098.5806776002923,220874000.0,0.01934913,48.19462 +130,0.0014419905172141762,0.04305727869946367,0.07179047047308584,197758607955.97266,7696.227062611864,967.7734101498513,178660000.0,0.01542724,51.32497 +131,0.0013114914730324918,0.04874292342748751,0.06476364340557611,200317760288.19666,7867.015350466382,903.5661567056329,169733000.0,0.01280713,56.69805 +132,0.0011763736813083599,0.04772053763175818,0.07633606291822068,216137948625.67963,7885.7836443047745,791.903168687453,147498000.0,0.01051969,58.96296 +133,0.001477364728731273,0.04798118809784911,0.09711761321839203,199425746494.1215,8013.980965852183,1214.601414690902,148417000.0,0.01136437,57.55383 +134,0.0019405496333582983,0.04659050871551539,0.0913009135690834,221267799278.0209,7921.109980542882,1130.0874033423659,117972000.0,0.008391213,58.40195 +135,0.0010137479473165067,0.04697082612818761,0.09250455236584562,211057893924.44907,8048.492707298418,715.6098706288856,132834000.0,0.009825069,58.20305 +136,0.0011759919847905333,0.047407403127131695,0.07712055323344556,207599075281.6106,7754.873090024482,1087.7232373602951,202562000.0,0.01513759,57.99004 +137,0.0016822392393997447,0.046308696785127994,0.07835025663761114,213850876896.59494,7771.949480303628,1127.686518869331,153611000.0,0.01140235,57.03368 +138,0.0014074104896135945,0.04594410964232023,0.07897310038585259,216668728761.926,8023.4042862253145,726.1224633523758,116673000.0,0.008614212,56.46961 +139,0.0013202522230041924,0.036418868077763775,0.06515113838934117,208091252141.19373,7842.603833499941,1038.7868550836802,277656000.0,0.02692092,44.35309 +140,0.001852548460730569,0.0343195902393685,0.08393932622102523,219611160804.68048,7896.5807276120995,1000.5825003377374,171252000.0,0.01660204,43.35496 +141,0.0019279750602441176,0.03236734297197283,0.08211838159463633,205931442699.87476,7772.905928036911,943.7324122939896,171303000.0,0.01876819,39.81932 +142,0.001179209985519919,0.04126342706246447,0.08358805006749055,202882691525.93213,7861.821558238178,1096.9605496085205,225316000.0,0.01973357,50.55017 +143,0.0010402820342788846,0.03754714702121089,0.06590302820739813,224127113937.82776,7979.266505095437,1270.145961119427,402599000.0,0.03516036,47.34532 +144,0.0015432418946099255,0.043177387298473424,0.07058066268715842,195968820481.6343,7880.217039869974,846.9160794176302,148653000.0,0.01291986,50.4112 +145,0.0017897907698063906,0.037259456961528395,0.06449075419685432,194984133179.4927,7892.900137522839,806.5547231595023,161441000.0,0.01633296,43.12068 +146,0.0011025127992697846,0.04377116695659505,0.08616166202930342,195643599872.3751,7889.280687466309,1172.9349146938143,232705000.0,0.01993085,52.57549 +147,0.0013272107402237,0.04286051123041042,0.07755130070968815,195209330743.83765,7777.590860022376,785.6778702929588,147350000.0,0.01293582,51.06307 +148,0.001552129237260867,0.032991164950761445,0.06502426062553995,195713595581.19818,7852.605893017407,789.8426695683685,207144000.0,0.02354884,38.92176 +149,0.001299674256998891,0.04723935576535783,0.06085284177966328,222763157112.5701,7961.756403254642,887.0812905938742,203725000.0,0.01287582,57.3638 +150,0.0010804929908739996,0.03538828270213096,0.08741775093145028,198315853832.88483,7916.814233865129,741.3735852383602,190994000.0,0.01987739,43.45706 +151,0.0015410631667175066,0.03670666016576524,0.07703638615064085,202356295606.17715,7980.040667652364,760.4169769590968,151639000.0,0.01496472,44.1631 +152,0.0017650089868867427,0.04279601508533494,0.09637980509675603,199289365823.5298,8017.235196447393,908.9479470432825,109425000.0,0.009379938,51.33874 +153,0.001754196716292867,0.03743655553010297,0.08840141066535163,224863290060.70624,7945.928177859497,871.4346477568866,134129000.0,0.01164538,47.88287 +154,0.001881391066947048,0.04670409566285368,0.07551224176890356,195120794212.95386,7632.383243820923,831.3491627172374,104596000.0,0.008441637,54.95187 +155,0.0016696693773532653,0.03454688280292159,0.07310717503800142,209960364678.95435,7740.415772139939,979.9178446869764,205507000.0,0.02077013,42.75992 +156,0.00172540793427418,0.03220263490506482,0.08884761160284588,198136798951.9779,7776.227647012325,998.5518237669831,187022000.0,0.0213494,39.3694 +157,0.0017021169222865239,0.035775656407852026,0.09952831023340611,201682972304.8045,7888.735460188848,1092.5842257567185,164546000.0,0.01658161,44.0992 +158,0.001073641015855308,0.03482911155447284,0.08852401125600447,221832831871.88837,7942.289754798664,883.7783984207174,230871000.0,0.02180959,45.26593 +159,0.001908250849720289,0.04840378440275349,0.09331753196845698,196352611551.7294,7620.75520349845,1115.0064951245777,110586000.0,0.008533694,58.29225 +160,0.001454119961978385,0.0340560324021702,0.07369781130293379,222863763693.50986,8036.509392084488,1108.564305200581,265368000.0,0.02562342,42.95339 +161,0.0017129268639728458,0.049336355728950884,0.06854945961336864,211271203530.2992,7610.047750284154,1159.0215630884964,160414000.0,0.01133506,59.76948 +162,0.0019460935889501897,0.04257811123240244,0.07787564667583681,198455459792.26538,7757.7468153938735,1208.7639345054865,161550000.0,0.01404004,50.53095 +163,0.0012737620933785197,0.049822993457325354,0.07661427736068141,214877430955.166,7827.534985868011,753.5194585057056,123220000.0,0.008470432,61.26916 +164,0.001248936227817308,0.03231725939799385,0.08432091168811844,200162718449.19073,7623.258385367269,1199.8374888435833,311733000.0,0.0351699,40.51074 +165,0.0015136942457557475,0.048598856029102154,0.07245268903460947,195316492531.72122,7767.657596323189,778.246651563405,117294000.0,0.009096162,56.69472 +166,0.0016927545825100236,0.03740896981094918,0.08233650916929101,204755163177.22437,7823.076216683907,826.6744124764956,139844000.0,0.013368,45.78586 +167,0.0014187219727014716,0.04636391688418688,0.0620805305201271,224980337252.9825,7999.93793689346,863.8500855501562,166492000.0,0.01175738,56.53747 +168,0.001871741939885636,0.04704251623843433,0.08826941737495758,198022495082.5901,7636.078147549845,1287.6170122064705,141190000.0,0.01112311,56.66618 +169,0.0011925476397432323,0.038103400420514424,0.08562720389590966,192198021325.02988,7712.527216027241,1242.4599266864507,272336000.0,0.0272144,46.22279 +170,0.00163131457271449,0.042217619521955026,0.08746575247512962,214300779470.70844,7824.9597462600605,1045.0604407738929,149325000.0,0.01209373,52.87032 +171,0.0011162150246206255,0.03960501372971878,0.06424065538268019,215932801033.1882,7963.340004281199,1042.8370236626345,296419000.0,0.02549245,48.6212 +172,0.001334104928800668,0.039834811922411964,0.09538091914298508,218461780185.71167,7784.438181069937,1152.736260957974,196773000.0,0.01651178,51.46689 +173,0.0015209821316964974,0.04042734489223126,0.07692645592096578,221524704974.12997,7918.6576375667,935.9102119981228,167821000.0,0.01375515,50.7931 +174,0.0017951198997835328,0.04175147094734076,0.08047266210567527,199753275705.37973,7710.344482261657,1295.6045083429478,185214000.0,0.01629598,50.27361 +175,0.0018579604671093146,0.032132244313663814,0.06190417521221926,211435529400.42194,7617.1393695986035,810.1039835473287,196506000.0,0.02123775,39.48781 +176,0.0015666643016823039,0.040851342939075504,0.09181798779795186,196570294185.26367,7814.716479233401,1067.5903161431809,157912000.0,0.01438314,49.43597 +177,0.0010301793461796294,0.04189270919331473,0.06395663367720394,224180902622.91296,7680.047077531514,891.2679274205354,255579000.0,0.02002602,53.17874 +178,0.0019194897861379452,0.049725482786351104,0.07933436070706576,214081671012.65158,7793.600323864836,952.3774867350868,104316000.0,0.007208376,60.63727 +179,0.0011119535448353066,0.04195759778793915,0.08768999139093421,212217284078.88968,7648.556057113604,1002.4768750006771,204515000.0,0.01682836,53.56637 +180,0.0017462920312477056,0.04520040023574237,0.0989164361278367,191925712302.07352,7770.3677974998855,739.1990664796494,82142400.0,0.006925448,54.02975 +181,0.0010915115899984684,0.0350271438139308,0.09096609004004012,222630967397.99664,7677.021795569235,1222.3060101690644,305025000.0,0.02852727,46.4586 +182,0.0019304706655428761,0.048352546756689346,0.08876155138952463,207888048346.384,7695.759773723851,1288.4262169667688,132188000.0,0.009654109,59.29403 +183,0.0011314213597277954,0.04642671889725072,0.08627312309007232,195851301232.82425,7780.781143391206,1153.8207327798966,207879000.0,0.01678317,55.88252 +184,0.0018142788601614112,0.0409579628172916,0.072239399437376,206216470902.20294,7612.20248916432,1175.3929913605189,187117000.0,0.01627822,49.90726 +185,0.001291723341859768,0.04154812390755741,0.08575074366661525,193612695618.68906,7857.001390484988,856.9688910121229,157012000.0,0.01430492,49.70642 +186,0.001715692072834845,0.039904779188028316,0.07571042651792241,204214696945.23108,7678.019178854467,1105.5164312360903,183449000.0,0.01652507,48.61534 +187,0.001939724004965304,0.03722753343293975,0.08861834942781834,213598060565.83456,8049.336546742771,1067.0657862371431,151196000.0,0.01389398,45.9032 +188,0.0017703349414301519,0.03703365117031971,0.07628612036842564,207531347055.00085,7657.49923385092,705.1766045012619,124144000.0,0.01184369,45.7135 +189,0.0012386946265302615,0.03858828648780099,0.07256004624726206,204103847841.8155,7667.904800913039,911.5265880412376,219216000.0,0.02043719,47.49902 +190,0.0012620466718435668,0.033716540250045356,0.075403103289133,205860265615.2629,7608.948580845443,1134.7932826825318,306003000.0,0.03229125,42.35507 +191,0.0016712933246098632,0.04403165130051327,0.08897343039397744,204012513844.93814,8085.58333400196,994.814145181311,130136000.0,0.01061867,52.82385 +192,0.001064502358440666,0.044643149980381665,0.09196069940428869,222977487828.63245,8084.856091303922,1061.3451174644092,200983000.0,0.01479317,56.87491 +193,0.0018258078095340561,0.032678473081206035,0.07467729279893809,193845273706.38687,7816.9471928985995,1141.599377507001,233240000.0,0.0269585,38.6695 +194,0.0014351273666806115,0.03550011354027348,0.07095721293197761,212634341967.5474,7844.176872682584,868.9983989817066,206664000.0,0.02008854,44.01982 +195,0.0017337062725303572,0.04226501116494966,0.09561415003120399,222162274076.3615,8081.180800120706,1236.2886927070906,154633000.0,0.01203957,53.35685 +196,0.0018772110369641225,0.03589743928798017,0.08837940384325409,218367336975.98965,8054.495821395609,799.899680814462,122291000.0,0.01139212,44.87243 +197,0.0013810706407242853,0.03666675544375202,0.0796624648626817,215185413446.09955,7758.248509189429,1155.8573075823592,247279000.0,0.02295791,46.48976 +198,0.0011538280352997783,0.0387256535108592,0.0938259722790837,202682382466.4035,8037.6014933765255,1208.8115338511675,247439000.0,0.02301937,47.65416 +199,0.0014757255774844188,0.04954447256627503,0.0716441016394239,196412495727.6312,8031.124915800946,1046.829285225006,176097000.0,0.01203887,56.87275 +200,0.0011042542820824516,0.04560107717555474,0.09766233616630329,191704074828.46335,7699.710916418362,1014.7183298430633,171913000.0,0.01439266,55.3833 +201,0.0013548671586983807,0.04468816728433435,0.0864137162460129,214660262705.97803,7625.9734222350025,768.3406490354248,122503000.0,0.009368664,56.79521 +202,0.001068796883095088,0.0491184970852776,0.09021918522757257,214610694073.88797,7874.242089681267,723.0080662184105,123599000.0,0.008608195,61.58372 +203,0.00106287755386422,0.036378126698689145,0.06605834425908039,205643465707.04614,7669.685250381435,845.7230858355433,272609000.0,0.02677237,44.91504 +204,0.0019426466952349808,0.0360296211484069,0.09991991460279818,203425140600.28116,7843.105488502281,1271.6244510841257,168623000.0,0.0167273,44.43963 +205,0.001015656079296126,0.04399195049737435,0.08484240352477701,196616673577.0325,7705.0644580561575,867.92861596938,187271000.0,0.01588605,53.5987 +206,0.0013075696434507425,0.03309491223229443,0.06614436107923344,209902433109.35538,7654.785907710663,764.6810116352814,229042000.0,0.02419799,41.3182 +207,0.0011655860296807183,0.03248878157476621,0.09054590893606615,214000571558.25537,7969.139718113781,991.2155233556832,255881000.0,0.02679831,41.53135 +208,0.0019132784826269636,0.03894601026525796,0.07597141043011985,221901463497.5875,8066.389514932658,1084.9535316427932,168209000.0,0.01428211,48.10074 +209,0.0015242232690938745,0.0379425320016469,0.08111201351542585,202452481793.0598,7644.884673561889,752.0565168924277,139017000.0,0.01325902,46.81396 +210,0.0010477560620715807,0.03837731423655451,0.08529965827880498,211362613379.92334,7797.473939473114,1100.803987346177,270518000.0,0.02441223,48.69198 +211,0.0017299116497941117,0.039041554461289305,0.06317408506648499,215763594815.09012,7838.4231390415025,1056.6325708414663,208967000.0,0.01824455,47.51581 +212,0.001817659317612683,0.032576769170819586,0.06260010883624989,213345442954.66107,8040.029115271454,932.3431211665394,224177000.0,0.02368403,39.21115 +213,0.0017212049499508901,0.03777494890269902,0.07734975579115617,197187524006.15118,7988.806791677554,956.6760598250714,166386000.0,0.0163791,44.56947 +214,0.0017682677107873379,0.034678784078019415,0.06525130617667776,215101393143.58432,8065.568480996312,1247.6402209814264,273610000.0,0.02693658,41.95177 +215,0.001224146461217022,0.04175841993369235,0.06340868064163754,219026951627.82816,7629.548953499815,1122.918478923486,277586000.0,0.02233429,52.29381 +216,0.0019150763641771005,0.0395002848772466,0.06659564807812962,219867549911.7314,7672.654269194238,748.7456886549284,127651000.0,0.01080555,49.06444 +217,0.0012218107204635012,0.0496700217452331,0.06250215089582836,222592763230.19345,7812.78546102744,1203.3247931073101,242572000.0,0.01616986,60.8516 +218,0.0014684889209150532,0.03865073279495557,0.0742538568094461,212125074659.82623,8051.402605470927,779.5972628092101,157224000.0,0.01407581,47.16225 +219,0.001325894848019354,0.03952596128392322,0.0817286012212588,194835326971.96182,8098.42102247525,722.794457441388,143199000.0,0.01363033,46.62847 +220,0.0013639229160013532,0.04623150492299589,0.07003498655889344,209388142729.39557,7720.2565400387775,726.577425595517,131888000.0,0.01002717,56.22131 +221,0.0010870071650351215,0.03389123273214714,0.07879956215129323,194284934520.12546,7689.669283933254,919.745483656261,271690000.0,0.03019597,41.50894 +222,0.0010452538788248839,0.045155657195217296,0.06989837265694405,205458758818.11905,8063.225092096619,887.8040914253845,212689000.0,0.01686906,53.69528 +223,0.0012589270241524955,0.03455724502567466,0.08576291360702223,217800105051.48083,7869.812390707235,1055.6868727668382,247429000.0,0.02401201,44.36914 +224,0.0011542437051506888,0.04775451929828784,0.08103191150672516,220323232596.8432,7971.518236448638,1278.922728783569,230431000.0,0.0160995,59.66132 +225,0.0012960357114073643,0.04439814038850221,0.09716600164914671,204324101094.2507,7876.582380707346,803.0579211001856,121564000.0,0.009803174,54.89609 +226,0.0013704104988831649,0.04881784219310527,0.08029724822923413,214548734236.7113,7604.125665743665,1116.3712355733664,168213000.0,0.01181036,61.1736 +227,0.0014649590851124657,0.038787307674271465,0.08426478985843372,220578562580.98953,7730.331669259205,742.7479191905502,133969000.0,0.01146673,49.86755 +228,0.0010707625704436607,0.034806556579644585,0.07484306182422523,210403442437.42123,7688.785673597145,1235.032451614985,374640000.0,0.03749355,44.0941 +229,0.0016776230513939755,0.04681961092118972,0.0663922551880556,200853687614.29892,7796.595005938504,882.6292364769919,136597000.0,0.01069592,54.72436 +230,0.0018120511657537825,0.039665093529919905,0.0897878664425652,222279001363.9281,7922.365765566034,1160.285168654371,159403000.0,0.01322338,50.36814 +231,0.001353137847284724,0.03286270764010374,0.06323723193989686,190770451990.2249,8075.0412924740285,763.5515209253895,232657000.0,0.02724989,37.87575 +232,0.001498588405992741,0.04617313646479891,0.07016776086772751,200776841967.80035,7748.064125897989,1181.3950291074548,196774000.0,0.01562109,54.74492 +233,0.0017357649912354948,0.04049384632385658,0.09865322338685385,213159096963.03177,7694.465101847024,817.7011061705456,104784000.0,0.008857571,51.58395 +234,0.0011948651037729013,0.04976969960114698,0.08412439871561168,190975581798.05072,7670.0312476786385,1131.6272149779027,181885000.0,0.01406739,58.98719 +235,0.0015476212730166543,0.048477702297133746,0.06416285014440869,221703399976.25583,7914.132696959499,862.3505873267183,141245000.0,0.009682122,58.82694 +236,0.0019443399616416819,0.041288867688546754,0.09721262762755865,191613963235.41055,7948.873064743209,1119.0450537217996,128290000.0,0.01184212,48.70318 +237,0.0019109086486118379,0.04951243415857713,0.06792808627389435,204771973732.69473,7693.295161676957,1028.220473932634,129428000.0,0.009403007,58.41758 +238,0.0012670871922841965,0.04757409170753997,0.08685107207763418,191162594521.0821,7639.910246764749,1013.1070766976875,158553000.0,0.01280154,56.85573 +239,0.0016064402603122555,0.04731994137585487,0.08696554524091643,211937144529.39844,7741.964824017816,1169.010971990419,147909000.0,0.01082782,58.77314 +240,0.001033588360413974,0.046508873503121974,0.09142250366190788,217333897102.65985,7763.06382306836,712.0273687241028,132645000.0,0.009623151,59.50142 +241,0.001587527279129233,0.040746231764825,0.09694771185339698,216805567006.4908,7702.804875758908,960.9177163423556,134389000.0,0.0111082,52.40608 +242,0.0011841751024074087,0.04522866012018701,0.09730992083546408,196845869292.38733,7807.199416379326,1196.0586533097933,192294000.0,0.01580625,55.19963 +243,0.0010542717263416145,0.035205841648147794,0.09677216793793947,216383659465.90253,7985.774071042083,1007.8745901211889,244553000.0,0.02336306,45.41204 +244,0.0019281086126963511,0.044478380846394557,0.09001833814290677,198782166631.54803,7795.817191229569,1053.615119057256,118624000.0,0.009833052,53.36313 +245,0.0013846936162299544,0.048103777277791346,0.06580521922025527,210563994807.81366,7668.278093220487,1078.9973523500648,193500000.0,0.01407148,58.2024 +246,0.0019221266606162697,0.03512523547115469,0.0738314150109734,194095164660.8344,7704.48045040189,900.9901685022661,162483000.0,0.01747186,41.603 +247,0.0017231266650213225,0.04197218666385814,0.06740109755098635,196686543739.3482,7697.578233937285,885.9198090943582,151824000.0,0.01352692,49.32106 +248,0.0019628140867377006,0.036635793160199964,0.06442899128158168,203786441657.6636,8094.018612898747,941.8371939051848,178081000.0,0.01752877,42.64105 +249,0.0010662064892568185,0.033782879797763614,0.08367517353451406,204852445051.77344,7887.351816270333,1145.4618915429112,328089000.0,0.03463876,42.21396 +250,0.0014731436602729822,0.04425303427720712,0.09169150167969628,194505825418.99896,7738.652326365164,1216.4049644867605,172765000.0,0.01470387,53.36115 +251,0.001906656291622772,0.039705418869349765,0.06357032701427666,192490244276.27402,7951.187877038813,1137.3930852223086,201202000.0,0.01936283,45.09471 +252,0.0016605268538137687,0.035655450459279675,0.06878276038865498,204367835731.00534,7906.868844830815,1018.9616996037624,217676000.0,0.02192945,42.75661 +253,0.0014487463500344317,0.038957098287193084,0.08968842496905058,218820918945.18088,7903.8892757414,710.8527440085728,122021000.0,0.01046652,49.63433 +254,0.0014676911538239557,0.032532883988638836,0.060776693643715324,218872942605.21817,7800.117174433966,1003.8873447727242,298385000.0,0.03078067,40.58268 +255,0.0010385836848923148,0.04830169349955719,0.08117138538947792,197316511655.17392,7911.852574046515,842.4718843574481,165052000.0,0.01273206,57.40784 +256,0.0015633833335920943,0.049995418056860155,0.061315980824647336,202130648026.24332,7759.930068368523,1143.4226522778843,185094000.0,0.01350064,57.99346 +257,0.0014944355776094645,0.033125072355357714,0.0903347941523025,211738623155.67807,7789.179442917361,937.3110963145429,189467000.0,0.01967827,42.1398 +258,0.001531278378877165,0.047689651199248964,0.09476260123684571,192632555798.4309,7656.759072883586,851.497863148923,103589000.0,0.008265586,57.33982 +259,0.0016333723064557767,0.04379817904411475,0.09166952877353196,212719282480.87387,8058.697035371163,736.7568725903908,96517000.0,0.007587528,53.96617 +260,0.0014975280054747588,0.04338452640236649,0.07456559973221162,200255144770.30478,7877.357001576659,1019.3593932680719,174443000.0,0.01475773,51.5534 +261,0.0016800023407253328,0.04565791340654973,0.06360881764267681,196936397422.7411,8027.2085485121925,757.0468823307123,138656000.0,0.01025191,51.92834 +262,0.0017058376919991522,0.040315431000004155,0.061241823949337366,223215397982.6526,7745.592637612137,1248.9181991784503,246440000.0,0.02015125,49.94465 +263,0.0012008956745933911,0.04032188193052282,0.07447130743173902,198594237563.37262,7836.206995021041,971.3825957593316,222763000.0,0.02042982,48.44272 +264,0.0011280103515727584,0.049392575167410066,0.0838488080794342,198097995644.0159,8002.5983383659905,1148.4782978742153,197176000.0,0.01481323,58.46617 +265,0.0012927874012036147,0.04654620300724855,0.09573242872907908,196261009875.2683,8067.703700369547,776.5962400314728,112736000.0,0.009039997,55.46164 +266,0.0012436096289403626,0.03944991396759663,0.06016405651815926,215451798427.40448,7917.165879107998,1266.7824046327723,346209000.0,0.02997263,48.02934 +267,0.001173616157677178,0.036493683359123154,0.07153504094387916,219171470180.25662,8007.086714816298,1050.2319335143727,287563000.0,0.02638679,45.75074 +268,0.0018666445827710911,0.034850678940462884,0.09814117948892676,212800665484.55804,7652.03548111386,992.4824828735846,144223000.0,0.0141373,44.58579 +269,0.0010111400859002,0.037096429438643275,0.07101975722000563,193375694623.9494,7643.228150656771,1161.5460183829314,359877000.0,0.03683115,44.81711 +270,0.0016203297484178388,0.040776329302361636,0.06623965553794658,217470018284.6561,8078.005922227548,1151.1536268282077,219292000.0,0.01818723,49.31593 +271,0.001405584003852583,0.03410531907287047,0.08689520474108442,219743767059.64474,7891.604765346241,1219.903509039244,260058000.0,0.025328,43.82029 +272,0.0016752569135831874,0.04383720188886288,0.09430436033380828,205581296584.91595,7735.5205829251945,1214.9658226986207,151645000.0,0.01231566,54.29385 +273,0.0019007154951628641,0.042343145529637444,0.08928136102627714,224551170959.17892,7929.596645596751,865.691983638729,105678000.0,0.008140125,53.68218 +274,0.00164550824882599,0.046193726651133096,0.09969963414894256,221213154840.17883,7746.531768594306,798.1593250344141,90509600.0,0.006479916,59.46216 +275,0.001399629249454395,0.036879302188023336,0.09269684562339936,208704275768.6662,7941.09239510861,1079.436735273654,198509000.0,0.01881818,46.12026 +276,0.0012354679382547567,0.04088213116182721,0.0797396217319906,207210547333.27606,7732.056201631896,874.7175426422116,181245000.0,0.01569864,50.76481 +277,0.0017575506210509083,0.034957519842181706,0.06228806437578738,199867541823.22232,8093.83805842975,1258.4328995915725,285910000.0,0.03006973,40.49011 +278,0.0011404341894516567,0.035592406362014405,0.0650466083528587,220950430201.59634,7947.1516192459385,765.726806623841,240800000.0,0.02249487,44.65768 +279,0.0013298989280226998,0.036036772752578626,0.07437553598170091,219216675853.31784,7783.240033116299,1166.6460480897867,279978000.0,0.02598986,45.85062 +280,0.0018624383232909618,0.047194213587477406,0.08658173000274029,223919114407.60635,7854.123321861249,720.967050843913,80409400.0,0.005586346,59.48575 +281,0.0017505104581443665,0.03261846178602109,0.07010005177106408,213429850158.67664,7762.882156099095,1281.7139937647655,287695000.0,0.03029383,40.49575 +282,0.0014089942501260395,0.036733810030797454,0.07491338845205484,211602342871.37164,8052.21310861855,1196.9042154995834,264688000.0,0.0249748,45.02533 +283,0.001139506549893041,0.04489748516416273,0.0946657622517964,223353383665.05045,7747.811469766126,1015.8457212897008,174870000.0,0.01276894,58.52423 +284,0.0016120787474287425,0.033594013892039594,0.09344657120026995,202181107031.45364,7850.647949325761,790.4341317364884,142582000.0,0.01527759,41.54762 +285,0.0017310521345247241,0.032216481365112924,0.07039249537407938,194241133755.9992,7968.124460955472,787.2591384273682,180550000.0,0.02114532,37.71998 +286,0.0019544244142987222,0.047862219806139916,0.09440160171680878,221718152911.45035,7742.698679642337,1210.2757682637546,117975000.0,0.008149856,60.82574 +287,0.0012373262869123335,0.04450617093775224,0.07986732153852853,223401928321.364,7653.975549159118,1118.5358105306173,208407000.0,0.01539707,57.29776 +288,0.0018965761643788227,0.033239713163469096,0.06004035159374391,192936135303.03046,7673.651724116538,731.3618797217072,171461000.0,0.01964421,38.64532 +289,0.001917938214325928,0.04501772829552687,0.06912438813698633,192866438348.58185,7950.2502942489555,1231.2908648605817,171940000.0,0.01457194,51.19183 +290,0.0015062667823356928,0.045257141552757466,0.09200914358633133,200834885264.6698,7935.468707245182,1006.2648760677102,135866000.0,0.01095403,54.65303 +291,0.00142950860051401,0.04192769313301038,0.06111782542932019,217862903929.42172,7859.289983119946,1031.6373275751878,227159000.0,0.01830562,51.1127 +292,0.00198051201055461,0.033204861934926964,0.09844190929431276,222028532743.12134,7606.359560787896,1002.1581580295292,146626000.0,0.01443615,43.44359 +293,0.0013502238826099701,0.04986277691463611,0.08037203491108384,211519497349.2231,7726.85897270005,989.2331845024796,147107000.0,0.01025943,61.46137 +294,0.0017621942146247393,0.04712281168976603,0.09805342695618374,218968160570.48322,7714.788227836534,1092.2919983307459,115264000.0,0.008179295,60.14792 +295,0.0012457597787397972,0.03919743303676935,0.08984081415446361,201754815962.16415,7706.867457919048,816.6072944852912,159408000.0,0.01474083,48.79898 +296,0.001170839519566514,0.046120120980635504,0.07522952703937819,222068247745.07697,7721.361532472235,1174.5734498885163,231949000.0,0.01665606,58.46748 +297,0.0016405745006124774,0.048867893860935485,0.07925231489416762,204947264328.3285,7660.830285894902,1054.244113924237,136049000.0,0.009990808,59.21533 +298,0.0016994785924373283,0.048590472864331866,0.07134029182065735,218641055089.37595,8026.669798704403,772.3767401565929,106189000.0,0.007358541,58.67287 +299,0.0010282871807386885,0.0376132011919907,0.08057879804758576,194738463417.35345,7642.783377635816,898.3733182570564,241744000.0,0.02418182,46.10715 +300,0.0015281804067545338,0.049953379308486354,0.07195449329556926,193269711612.03055,7626.445714414273,974.0537655632148,141410000.0,0.01078551,58.2956 +301,0.0019887793292224197,0.04263355233069287,0.07814919361662259,209131452167.12894,7956.32524964362,709.9808942533398,92673400.0,0.007632664,51.25112 +302,0.0015958518588945227,0.03543991380167309,0.07076727718943635,209243930196.145,7603.777632072103,701.8708448418423,152613000.0,0.01510055,44.07172 +303,0.001992514187380284,0.043007465765228556,0.06905362000719001,194581794119.99823,7821.239578314815,1023.4329328199958,146641000.0,0.01288841,49.59989 +304,0.0014270016115491555,0.03474969010198233,0.07064576235134673,214255573119.3011,7865.471342461891,815.6776036437669,200948000.0,0.0197998,43.24253 +305,0.0016365249697254045,0.0499254133879225,0.08763874500491825,208306288588.75558,7883.078315015762,950.2077921034764,109926000.0,0.007765464,60.68115 +306,0.0012070633589306113,0.03934536080041275,0.061803953882911926,216097338089.87744,7848.783083877194,895.4857749539956,246705000.0,0.02134624,48.38253 +307,0.0015059988637414821,0.03716844995620398,0.06281846059349613,192258657349.004,7939.967850912197,1184.116020515687,282952000.0,0.02911077,42.79752 +308,0.0016876914337491731,0.036970121253748475,0.07110661080002846,222926135254.76117,7987.673761637836,1251.2301264088428,244684000.0,0.0217936,46.09112 +309,0.0012851754828070718,0.04273665285011698,0.09502154566997378,210936594570.296,7692.499790350686,844.2926882227446,137480000.0,0.01115472,54.37231 +310,0.0017423005396171977,0.04413204217000563,0.06310208434675248,195929917531.05057,7799.749566913268,1171.2845544014083,197111000.0,0.01678299,50.81534 +311,0.0010428351105169213,0.03912823318916675,0.07936077775303257,192553125873.80463,8092.360972789736,968.968035347558,248669000.0,0.02420446,46.12882 +312,0.001700134133083093,0.04498866101741614,0.08867771666168461,206461701406.1737,7912.696797923365,1125.4722291490234,141595000.0,0.01117862,54.75227 +313,0.001230028757796976,0.04726453831891805,0.07328751145513879,201284108615.86203,7633.730869499277,1129.2149799111194,211242000.0,0.01633884,57.0089 +314,0.0015569054343175903,0.03399581201451577,0.09299955977445144,191005433080.40585,7822.591676977367,706.3009910112214,130024000.0,0.01458007,40.96967 +315,0.0013933329001470434,0.049808095628109936,0.061429821923048654,208956790145.29797,8072.149479121079,1167.4644611937035,210637000.0,0.01491733,57.82756 +316,0.0015175442644268945,0.0394606637203992,0.07683568150993465,191608519079.20038,7962.683638542663,750.1875983066544,139023000.0,0.0134925,46.05566 +317,0.0010266882542780344,0.04354609888253731,0.0855948042576632,192376154550.75616,7996.418272702042,1206.3739318531552,258888000.0,0.02266782,51.59212 +318,0.0019703024157737277,0.03415440570370674,0.09619147469318437,190038871838.03925,7785.557148478768,959.1138434291679,138883000.0,0.01555914,40.76916 +319,0.0013003944954995757,0.04355985011054889,0.07583911866660926,191877497707.7012,7791.096095811205,1244.14060766464,237533000.0,0.02088503,51.25071 +320,0.0010976999208435036,0.04506069954370607,0.09290720110046102,210669262822.44272,8079.40057319142,1171.656221944105,211327000.0,0.01630949,55.79372 +321,0.0018030118727926764,0.03587719021116287,0.08180182136720193,205724823359.44333,7815.775584107325,975.9448441007919,165500000.0,0.01640856,43.96808 +322,0.0018392955750128697,0.03348638926348245,0.06631852497576998,212299257504.0756,7832.35599059855,983.6675150689908,214964000.0,0.02219242,40.89442 +323,0.0013964467031282227,0.03690282129023625,0.06818306353432645,198742804161.26987,7881.524977555996,1241.6759128510148,298549000.0,0.02989866,43.88995 +324,0.0011675088679223811,0.04103446813456472,0.08278211277432992,200118157391.87872,7649.586069680945,1188.1879996224075,250051000.0,0.02232868,50.60038 +325,0.001058351168914633,0.04417067527992548,0.08350389213447579,224234929895.91028,7884.982139040825,872.8900450327517,182784000.0,0.01354524,56.64813 +326,0.0012160961424198381,0.045643566132931426,0.09313895845625786,212052085889.9201,7775.094097781182,796.0351478065645,128214000.0,0.00970662,57.60937 +327,0.0015688993529886048,0.035201186378964126,0.0629720168641756,198987032022.09338,8041.385263255812,1094.2992956681696,269479000.0,0.02826916,41.06455 +328,0.0019908563160469578,0.0468624711279529,0.09578081839787471,212604748788.48538,7664.270219232956,1272.5812258928502,123712000.0,0.009095823,58.73623 +329,0.0018930550626448709,0.04432964610682195,0.09629502285816624,217722137899.13922,7602.266875525441,1085.9572137360396,117710000.0,0.008922994,56.79436 +330,0.001331650385227534,0.035304095134680726,0.0861228862171112,208554020211.978,7937.265853654239,880.9417075021851,190531000.0,0.018907,44.04083 +331,0.0011502808777331622,0.043470384781171656,0.0859318622253798,205033081028.14133,8044.381339388064,724.0879954143024,139560000.0,0.01148393,52.89326 +332,0.0010054842119817044,0.03471105464273891,0.09944101499040343,206299458119.89297,7739.9115977093,985.9909657690662,248237000.0,0.02519183,44.60518 +333,0.001967320528104502,0.0327174351988148,0.09076197555056932,208777236052.42493,8028.714877599585,1211.9979437469938,195234000.0,0.02080811,40.16143 +334,0.0015783086011987059,0.04573327741115859,0.07337258144249548,197248752735.54993,7967.04702483844,1265.6190464923388,196064000.0,0.01598703,53.21741 +335,0.0011357682022693615,0.04327213665242384,0.08316085878473353,210290085272.24188,7641.847309866159,804.7749592778681,162308000.0,0.01308867,54.56591 +336,0.0018299052549865412,0.036597682624463106,0.0881488169927624,203689124579.9268,7957.585646228005,708.9737482783734,108434000.0,0.01062719,44.46308 +337,0.001779440727341795,0.04431084643078239,0.0829108491114772,200613839739.79825,7908.34866500126,1293.8695996350348,168711000.0,0.01393138,52.77116 +338,0.001119972445289412,0.03335906140707613,0.09532347621416049,190182414961.8082,8057.189697444726,957.5465948269687,237428000.0,0.02721407,40.15334 +339,0.0010009391176407938,0.046918633438672086,0.06247825348866833,199203940663.62076,7727.655168740601,1063.0944638467802,277401000.0,0.02186484,55.24257 +340,0.0013957994252030096,0.04003581845963665,0.06560460765993531,220798441182.55594,7817.3986272371885,1139.483387119254,255980000.0,0.02129538,49.88709 +341,0.0016234303848227003,0.04460764799894493,0.0657094115959387,200924958615.74103,7819.018995188646,1035.9491846632015,177087000.0,0.01454372,52.28604 +342,0.0012531101244779462,0.03277230843964719,0.07766826054134339,223506459991.8701,7959.012352552243,1136.780523452389,311186000.0,0.03108247,42.1384 +343,0.0018070949841369512,0.04536166443361815,0.09605408789898245,205506414104.80713,7965.021954254212,704.2301477962292,77502600.0,0.006086031,55.17574 +344,0.001649738157375707,0.04143013802630001,0.0795243666742048,190751393341.07074,7915.495936093435,1284.291157880692,201869000.0,0.01874584,48.24609 +345,0.0011231526897588987,0.045070516201307,0.09259980696050954,199032020539.37384,7949.741164362763,1212.9945457123554,214693000.0,0.01753563,54.63449 +346,0.0014882809384163813,0.03363986291523607,0.06701764101011438,224450116724.56073,7895.610110531127,1238.1631320507277,320293000.0,0.03113193,42.54717 +347,0.0015380469458671697,0.04002128660138879,0.06941588794906238,208267825127.99512,8046.60816229009,703.3597798832982,138079000.0,0.01217529,47.86347 +348,0.001994639930129184,0.043513472427484434,0.07498601497980753,193551556720.02673,7764.6447821658785,1059.5813672175443,139293000.0,0.01215512,50.64422 +349,0.0014325664964996727,0.04969520566652702,0.0937310960880918,220040316409.66724,8012.428049374359,850.9434030144664,105651000.0,0.00708916,62.28211 +350,0.0015881998174437128,0.03619966458934279,0.08951688164979701,200535377984.32178,8009.876061945559,1120.9138402675412,194151000.0,0.01952725,43.86537 +351,0.0017265057782546252,0.03851129293471845,0.09401544446205168,222344416489.83084,8025.393467899592,1111.9475689695726,158814000.0,0.01353862,48.98088 +352,0.0017105728267801852,0.04452825891353827,0.08341267693902388,206578600041.41302,7810.480832765085,1162.560100228202,155275000.0,0.01239064,54.24711 +353,0.0019376441750234077,0.049041928060916215,0.08023887002132185,209395063211.82822,7926.735425722179,799.5525277499318,87529900.0,0.00626809,58.76178 +354,0.0012037835011170504,0.04304335103786193,0.09789808750651152,201057703300.03516,7946.720645187806,949.3684699162078,158691000.0,0.0134029,52.82209 +355,0.001303242758145131,0.04645813396145519,0.08501287938075178,192987045328.15286,8045.9102687222585,839.1697540634486,134119000.0,0.01098408,54.30557 +356,0.0018215957713996873,0.032080760620613005,0.07416073521875591,210585003167.74597,7866.495780644462,1202.1618113730665,253428000.0,0.02746203,39.4501 +357,0.0016953708368619281,0.047992138180209046,0.08649881946496539,197010900934.96924,7828.104495422803,881.8787809021296,104907000.0,0.008148321,56.95591 +358,0.0013164662488637856,0.03434696964576335,0.08381532323020018,220454953042.91956,7879.9056584973605,893.206876985004,206664000.0,0.0199461,44.18965 +359,0.0014704598185636822,0.043372642247408326,0.0789075016281516,207287607441.23502,8008.229813050556,1020.6409324940091,169276000.0,0.01382798,52.35171 +360,0.0019527951794938172,0.048241153025891007,0.062392169337582806,208880685331.05713,7890.585822478407,728.7490504566063,99802600.0,0.007297521,56.26618 +361,0.0018891035678248577,0.049162452315579946,0.09212858119903186,205230242815.76053,7997.120588266513,934.8755908353669,92788200.0,0.006748423,58.96889 +362,0.0011422125137425675,0.03382066504526674,0.09917377538234448,205294824156.54654,7685.582256713637,996.0911532243338,230493000.0,0.02410877,43.38943 +363,0.001060994641350408,0.04909453513760917,0.09045184560455817,190310394684.6438,8076.58659175561,833.411305166739,143232000.0,0.01125416,57.26003 +364,0.0017394865564769298,0.037797088223162086,0.06481722223833189,199102268488.18018,7897.16186344517,1177.429475288978,236217000.0,0.02307297,44.2334 +365,0.00154894707981873,0.03206588077323695,0.09393454181178981,223629108649.55716,7765.736550685643,781.6844812335914,153897000.0,0.01559725,42.10487 +366,0.0015977696278933716,0.045335700953475035,0.06146231675562666,190275880602.38672,7989.402143966794,717.0209185414443,128738000.0,0.0109918,50.72003 +367,0.0018338649453711639,0.04184555925304016,0.0794993154257481,203043595288.39243,7960.881322632921,1253.2755382831954,177175000.0,0.01530511,49.87935 +368,0.0010182105428365705,0.03613330492088448,0.0664541387292508,199548865551.7743,7835.281937663366,1060.6688127818077,356828000.0,0.03635346,43.58238 +369,0.001461687417356078,0.0406722505462231,0.09640001877304685,211108125351.49896,7998.127468250852,821.8211487944451,124725000.0,0.01060869,50.78972 +370,0.0018741552847229082,0.04790884066222023,0.08799087846469324,190564925746.8593,7618.052506954955,1268.4223390630416,136176000.0,0.01095013,56.5857 +371,0.0017852896702260123,0.04897571179154595,0.07292670706559462,191386696834.88284,7671.101624616061,1276.8960308791534,163259000.0,0.0128212,56.60296 +372,0.0010565894551810447,0.03995358239560154,0.07367689989347773,196044238866.91962,7779.660984142427,1109.613448728736,292324000.0,0.02740989,48.01423 +373,0.0013198119575956624,0.04038980535032303,0.06682011884264855,207998661293.6882,7826.8635294459555,1191.6158068373286,274272000.0,0.02400694,48.9729 +374,0.0015352633116715872,0.04472466178466024,0.09589625052115941,193915755848.04675,7766.631121619263,922.8945408224473,119894000.0,0.01011777,53.87383 +375,0.0015812281574170736,0.039984909456465684,0.07653904829003483,201506113336.16974,7990.611846165422,1298.3855399281965,228926000.0,0.02085433,47.63786 +376,0.0017829321887345552,0.03294421302052962,0.08124969056349217,209628873635.5211,7849.033797374632,774.9736238630584,148464000.0,0.01570992,40.81118 +377,0.001240180404846965,0.04662342778552677,0.06951530250546799,211319963078.99716,7953.301681592131,1262.8159457050622,249149000.0,0.01861419,56.1752 +378,0.001949083247417677,0.04250865972365144,0.07587503926602297,223591140534.55133,7637.248480439721,915.3907876030459,125200000.0,0.009676874,53.82323 +379,0.0011325531372632454,0.03373270297423237,0.09090968415862698,193667129246.95227,8061.245429704561,1260.5890539982568,318258000.0,0.03549899,40.76048 +380,0.0017442618924274462,0.033402688512955414,0.09651550477625617,192714861148.31418,7808.139654109218,987.2219227777559,162707000.0,0.01836743,40.40028 +381,0.0012551595550419352,0.046105228247787636,0.09853287810992642,223760735561.38217,7752.795640454789,981.887700465594,144570000.0,0.01025574,60.09819 +382,0.001960223942483545,0.03920848842741512,0.06894401640554734,211189538191.9599,7813.027929822869,780.750178615889,127829000.0,0.01134395,47.44432 +383,0.0017587789559915725,0.032420925374524734,0.06061606998846668,197977233346.69168,7992.271518133098,1152.3481897299316,294856000.0,0.0337419,37.65692 +384,0.0014519902853244455,0.037301738176837004,0.07992409009942807,206677044786.6591,7860.528359809376,784.616745700527,156681000.0,0.01489049,45.92736 +385,0.0014371277271547018,0.03767631282798403,0.08227826286286513,205089819433.00012,7666.401832119959,962.9222559033988,186935000.0,0.017717,46.91191 +386,0.0016020099811653525,0.04398516524156422,0.07476922532218709,213227350566.29633,7798.647879526075,1282.8550323777417,202595000.0,0.01587868,54.03592 +387,0.0010096509257367465,0.04792049326207684,0.06276029690057366,212475652809.24478,7847.967407556753,1113.9196263842416,309902000.0,0.02024562,57.71764 +388,0.0013051934528341394,0.032177110016465456,0.06069898163228681,224790977917.98462,7924.791040392515,1075.3622587103055,359862000.0,0.03654221,40.5808 +389,0.0011568527186624085,0.04436607206153145,0.06787356493622378,208453628530.5254,7904.173237640105,1230.2337033382692,280821000.0,0.02234529,53.45208 +390,0.0019331432147083171,0.034999168573456016,0.08009054108477737,218324129987.55502,8047.4322544009065,1183.5170320659913,198622000.0,0.01902257,43.34656 +391,0.0013236593618237763,0.03318551102615122,0.09818176952864013,191239835802.18945,8069.276254709808,829.5203220651481,173271000.0,0.01982481,39.89251 +392,0.0012142749043991834,0.041653920912868,0.08719288570055597,205818337044.548,7607.943711187018,1157.3756437922239,220597000.0,0.01885219,52.38479 +393,0.0011895305930218962,0.04310426216055711,0.0790859004637456,211644313213.164,8014.511101698529,1073.7992069912066,217568000.0,0.01751392,52.92445 +394,0.0014160218161210002,0.03887059533595703,0.06841354368490032,203506377517.80774,8018.522351230427,822.5363982112804,182598000.0,0.01696312,46.20095 +395,0.0018646695720687919,0.03301933227370282,0.06045893461741948,197670699764.6047,7938.633418936501,737.7742681478104,175895000.0,0.01979816,38.28093 +396,0.0018050755441397126,0.03332023165933243,0.09597556155101031,210492440297.58414,8016.634952753116,801.8200702264792,129324000.0,0.01340097,41.47539 +397,0.0012502557913959928,0.045478323701894,0.0990292970859525,220121429846.60538,7707.152333532002,1232.5149571935372,184358000.0,0.01347202,59.06584 +398,0.0012276987988113254,0.03441168977874318,0.07351621997289148,205346642405.61874,7871.012813667959,828.3461436130285,228380000.0,0.0236911,42.33587 +399,0.0018785038580818403,0.034949412962029726,0.0645926293740077,203877074730.23,7731.640795602732,926.7868783346672,192780000.0,0.01987259,41.8462 +400,0.001920809621295749,0.04715631519338696,0.09701939060617293,215886735610.5969,7994.8760741072465,1257.3336932561201,123832000.0,0.008908724,58.4517 +401,0.0017408071098012796,0.035823478249861866,0.07401975413811036,218077522095.36435,7940.1942273016575,1052.5032795003046,201227000.0,0.01888787,44.49737 +402,0.0014208882451902485,0.04890929316947264,0.06173361716963588,204564763894.1539,7809.318104094933,754.0304915040987,150894000.0,0.01003341,57.22693 +403,0.0019971661749265627,0.047604652247359255,0.06164103076640669,214422489905.97333,7698.763298005473,839.7668363672747,115863000.0,0.008362912,56.87969 +404,0.001523865251389734,0.04481846737054488,0.07319009250544453,191316141347.75082,7601.234678776062,1250.6836785498529,205544000.0,0.0176291,52.71833 +405,0.0018361994438007693,0.03805787476505399,0.08167451033143129,208641570705.71,8083.368371457828,1069.891180680712,166138000.0,0.0153268,46.0056 +406,0.0013143436042950213,0.04061413937972577,0.06731310589637832,212447427567.69928,7619.30272633817,938.851634098443,214092000.0,0.01824532,50.46661 +407,0.0013748249564389423,0.04734808358473637,0.07228502975662401,207447071543.318,7736.002332607662,973.4075328111187,165843000.0,0.012426,57.32229 +408,0.0016095951894409925,0.03975658024068808,0.08196148996821907,215719990115.08453,7910.950925952276,1285.1148682524035,211696000.0,0.01809372,49.57677 +409,0.001264834043475869,0.04079893311135302,0.08634741328305853,222709584877.96982,7750.35845279666,1032.6517082135326,196017000.0,0.01580319,52.84609 +410,0.0014387504787306308,0.03651664972414866,0.06195150428568898,221352916464.32727,8034.726843459274,1142.16712026321,294094000.0,0.02674863,44.92025 +411,0.0013462540255691966,0.038887554143973636,0.09463276735372225,191498540753.4689,7839.778262012363,901.9191801282535,158268000.0,0.01551529,46.89469 +412,0.0018223451182949825,0.03561463557418676,0.08587269099121987,219446298598.97525,7805.553749978839,917.3630699754216,149047000.0,0.01393572,45.30078 +413,0.0015905533593380656,0.035797288868250954,0.08541054140910014,221986473418.3198,7719.856992598674,1124.5683697725528,205506000.0,0.01890351,46.31326 +414,0.0014452532329134826,0.03596201082870525,0.09683958772841131,195443561188.57184,7634.894249862538,1264.7940333704764,224228000.0,0.02322689,44.55784 +415,0.001886368538728898,0.04599019965943722,0.08817819953551821,216543810651.6711,7913.889179230301,1017.2696082968479,114036000.0,0.00840057,56.9692 +416,0.0015260818078732578,0.03346152302930347,0.09191592447287977,215625368811.68195,7715.116333718427,875.7734302633492,169041000.0,0.01706002,43.16744 +417,0.0018088954937593121,0.04171237732030006,0.07825444393955643,218175097997.31256,7734.966122204857,745.9803820522612,108637000.0,0.00876338,52.24387 +418,0.001972099007460395,0.037910150582347556,0.06693782414878316,215056267244.05466,7801.059648587462,998.8755247301444,174213000.0,0.01570195,46.25838 +419,0.001976935169360574,0.03532487657317983,0.09116299199514313,221400616424.3031,8005.8805515640715,1207.4838965219667,175288000.0,0.01634386,44.62176 +420,0.0010235345357066477,0.040357880933129364,0.07822833341594115,216444131094.36597,7773.451385940678,1228.941842538306,313010000.0,0.02629656,51.29424 +421,0.0017196735866305643,0.040691720615298875,0.09933055253832027,215996168849.99115,8090.087794659591,1022.6188082422779,130566000.0,0.01083774,50.92668 +422,0.0011061731326803739,0.042047510361172775,0.061133672993333124,209555809985.78857,7976.379853342534,897.2329448080761,248877000.0,0.02079229,50.27218 +423,0.0018406190088975645,0.04639940664077854,0.08248335259366715,196469007736.69742,7718.218534160597,1121.9472617931265,134627000.0,0.01084833,55.07301 +424,0.0017539752055721685,0.04202564955224445,0.07795942479045473,198649410749.8182,7831.660282945838,1095.7002086946284,162982000.0,0.01433426,49.93564 +425,0.0015929449102510056,0.04570248998697104,0.08520240544935602,197407895088.45633,7679.680334432373,1255.2472474348906,169996000.0,0.0138312,55.0479 +426,0.0019509133816475281,0.03428602591074893,0.08154850076276605,200433499876.0598,7936.7147789145065,849.168674116894,142819000.0,0.01519648,41.04879 +427,0.0017939499195939897,0.03465455401498159,0.08780290242176028,216247830364.80634,8059.382330539446,1089.186027269918,181982000.0,0.01772351,43.23104 +428,0.001544506536570324,0.03817340331782565,0.09147168442680172,203972275432.4105,7755.958746967493,1071.8800577730706,175025000.0,0.01642003,47.42777 +429,0.001366690008151597,0.04020816000921491,0.07919873574086694,195770969155.22217,7638.248991247117,1080.6078702063833,209562000.0,0.01953136,48.69853 +430,0.0014129095901975548,0.04803368302471338,0.09994426200437803,215559872362.52145,8010.877710462797,1259.2885813644702,156229000.0,0.01104744,60.15783 +431,0.0015759479651928968,0.045505896808965524,0.09551386076587523,223166376025.18765,7790.996105853619,1144.6656919533568,142666000.0,0.01028638,58.56827 +432,0.0010493312815465653,0.04394602425518647,0.08676391278713569,218251834038.17795,7928.232338967301,950.928243632716,195477000.0,0.01494727,55.70136 +433,0.0014106948654964658,0.034443521226005844,0.07709636873642321,216466067719.22174,7830.911265506992,899.911772992731,209824000.0,0.02060915,43.58569 +434,0.001222114990761533,0.0491737760657071,0.08300710374583609,207033282217.83307,7760.543254401646,1200.6958473526765,193932000.0,0.01400372,60.27286 +435,0.001629936837338614,0.03404681034263883,0.09907086409571651,224703721341.62897,8080.333947700731,825.5785324870465,137801000.0,0.01308171,43.92455 +436,0.0010502267088837445,0.04269850666211984,0.0784405729759945,214165897343.90335,7713.728862113765,892.6068882678258,206853000.0,0.01661195,53.92257 +437,0.0012756050358962733,0.04672900809793325,0.06545415459067452,193296849523.21136,7686.122523018776,1217.2484844855285,271569000.0,0.01999298,54.34658 +438,0.0011635433275367362,0.04370245432553137,0.07275430294469185,201870425344.12573,7846.439626104032,1058.3888771720196,230981000.0,0.01925119,52.46686 +439,0.0018694228939860782,0.03368331268383281,0.06887340576084343,194165240457.1997,7761.112238324286,876.8438176368708,181477000.0,0.02035516,39.62171 +440,0.0018309580487344884,0.04145257065494297,0.09828284786989464,206645350585.66995,8006.177064122807,1076.5619404440622,128264000.0,0.01093261,50.77441 +441,0.0010825667544865454,0.03449264053958614,0.06379401047663778,190079595153.75317,7751.708949647549,959.3420593345086,334329000.0,0.03745891,40.7236 +442,0.0011365411790808014,0.03416724500620058,0.07386683362323782,213040755988.8107,8021.0069011372825,803.8958045745704,238832000.0,0.02404721,42.56287 +443,0.001736372168632581,0.04230519512865735,0.06868377038674303,208345113570.01874,7701.605574070428,1146.9214409004685,190220000.0,0.01587196,51.21182 +444,0.0016537881152860181,0.03569987209301362,0.0860127443063573,213002241309.40198,8039.605915910584,720.1048227781713,126788000.0,0.01218446,44.29078 +445,0.001093016779331405,0.048638990589391376,0.0720883868705781,222545091989.7569,7600.42517926876,1048.6392719585292,214310000.0,0.01457553,61.71351 +446,0.0018267714362990568,0.035367898075271305,0.06780810841433173,209496968350.12994,7841.418000871244,830.1988201467618,167004000.0,0.01654748,42.87556 +447,0.0016550557110979586,0.041114557928841514,0.09522034816070098,207126103505.40732,7769.199422882484,1026.5756191971946,138949000.0,0.01192299,51.26954 +448,0.0017676930155892515,0.041231023431377696,0.07645717504051207,198208257937.46512,7991.7612146706415,811.9943157709923,124920000.0,0.01122388,48.39154 +449,0.0019754897506878847,0.04929759882451652,0.09056410828256671,219793308472.30804,7944.124261514036,1186.0156665025554,114380000.0,0.007749141,61.16982 +450,0.0010522502983023898,0.03941002882292996,0.08417442734243719,192447749542.34296,7923.6066981163,992.9551199520185,238148000.0,0.02300238,47.17491 +451,0.0014226224955744802,0.04108882134461584,0.07620897087799339,224383139213.74707,8073.290441596052,878.2379136164611,165287000.0,0.01316435,51.46942 +452,0.0017602150313255807,0.03328772187135853,0.06542636867926731,220615003736.49664,8056.028005657298,921.7032921120319,213219000.0,0.02131171,40.9142 +453,0.0012055006592421324,0.03750297804147336,0.09881115491557811,221064152172.1712,7811.496821473275,953.2109530263974,186153000.0,0.01635355,49.14689 +454,0.001987975718198156,0.03273596650074161,0.0847590168765836,218573478683.65128,7744.644718460448,1072.7075903693212,181879000.0,0.01854917,41.59124 +455,0.0010756641240388023,0.038273330651661115,0.07857722328726495,205180433567.54547,8087.606542926012,1184.928514005565,306042000.0,0.02857597,46.58399 +456,0.0012139945854526485,0.04365324467345759,0.0979555966171958,215246613429.8238,8001.727954810823,751.0479550181161,122415000.0,0.009525292,55.17292 +457,0.0011136369537837806,0.04332093349132546,0.06330346432629494,218029729353.316,7919.906890669142,1110.5867096630013,317444000.0,0.02233733,53.07443 +458,0.0013376500443250251,0.042952466767978,0.06653115479113793,210735206705.63608,7952.793727096595,1029.7760054755654,217687000.0,0.01769538,51.69353 +459,0.001843005948307565,0.03617481430347086,0.08094197621289742,199609319142.3471,7829.162616521695,1157.1030575632747,192327000.0,0.01949838,43.52351 +460,0.0011142358445811239,0.034239289380654084,0.06153042525634344,191771110001.17947,8019.325846932706,924.9698881199861,326774000.0,0.03656971,39.75719 +461,0.0016626944255510173,0.032866912052075606,0.07047195823988667,197467896486.55078,8033.927545026404,966.4662337936672,223575000.0,0.02525333,38.6906 +462,0.0012291094690680012,0.03355968759905566,0.08961271039483966,209670629982.63464,7768.8382175422075,1041.0405848731382,248565000.0,0.0257504,42.81855 +463,0.0016582251542037265,0.04809024048643337,0.07113967275518515,197535169227.51624,7981.3514032085295,814.5831421647533,116279000.0,0.009010763,55.42958 +464,0.0015194805154958267,0.03854713203096786,0.07855064422135287,212042767399.9604,7934.362209959865,745.1783069783205,139296000.0,0.01249672,47.58691 +465,0.0015615667331235882,0.03201109213666259,0.08490505203473869,210332258219.535,7804.065042183134,1069.1375029222704,229008000.0,0.02481027,40.30693 +466,0.0011683282740574613,0.040133294981314446,0.06930081351247692,194410808681.03177,7875.153401783712,978.904402336828,246191000.0,0.02319251,47.28439 +467,0.0015727464678713457,0.04282328157622027,0.09514564118253935,210176072323.1773,7833.001771518882,1180.6090444995993,159292000.0,0.01294441,53.54109 +468,0.0017878773918684533,0.03680254911377369,0.06839047806800143,193132866446.95108,7958.671220088694,1050.7227641522218,203259000.0,0.02100174,42.50944 +469,0.001257632383752243,0.04117403289391368,0.0986364028606001,216888873204.45523,7609.306494575644,962.752064241259,161942000.0,0.01323822,53.76311 +470,0.0013799434153887239,0.0432118877253269,0.07325739724789707,201228229155.46976,7855.565395824319,913.6920249252277,171821000.0,0.01452604,51.60022 +471,0.0016794243819700774,0.044107550766736205,0.06015567565507051,195043089762.60678,7787.019242532092,1097.5131104451802,198690000.0,0.0170089,50.50457 +472,0.0011250695750568728,0.042144080236184225,0.09873499869278017,191978455213.1997,7825.778045960439,913.0585416470112,165462000.0,0.01493728,51.13424 +473,0.0013430744021243664,0.04745024922020098,0.09122529242525328,198354978271.9522,7621.350172577437,824.4754359047411,117620000.0,0.009167288,58.05041 +474,0.001279256577480341,0.03678635615772428,0.09065704470913503,202849690124.9381,7684.561761642913,1082.5055724820797,220902000.0,0.02161466,46.16732 +475,0.001483581145437081,0.041806838410535514,0.0871013957587837,221635505380.3904,7820.232492584019,1011.5482366787123,159935000.0,0.01264671,53.45694 +476,0.0018612395288322013,0.03594447254186543,0.09505479641412737,224767541417.2842,7622.241963788742,940.0725461910645,135572000.0,0.01222404,47.18085 +477,0.0014426782636081505,0.039112459481077624,0.07802017682214826,214747947184.1174,7665.585572728892,1290.969660203713,249805000.0,0.02181623,49.45245 +478,0.0016353645553303816,0.03516424699221392,0.07172681183955791,191461330018.32095,8024.589558526444,1036.5316795345439,220156000.0,0.02398593,40.74519 +479,0.0019781385776375887,0.047010695996082374,0.06203028332747984,209777958536.93448,7978.979738871796,1010.5005421888735,142167000.0,0.01061976,54.69482 +480,0.0015017630294952905,0.04488019541075367,0.08189803438052312,201941908556.52325,7856.470960606511,860.7988120072883,130089000.0,0.01054049,54.01444 +481,0.0015836762612787426,0.036352631610118084,0.07747602612805898,198936964759.0719,7630.4516022639655,740.5338120373904,145159000.0,0.0147096,44.34643 +482,0.0016899515412918248,0.03844611079155427,0.06759133535030488,202591119887.64267,8035.8534531734895,1224.422468171208,237874000.0,0.02244304,45.20233 +483,0.00151076467951966,0.04292202956173866,0.07504106414387851,224013809286.15076,7943.025499107458,766.9551842241351,131239000.0,0.01003005,53.78562 +484,0.0010027538569928759,0.0402648769916491,0.07028928385492182,193154812967.00644,7681.850772271559,977.2035171867085,278671000.0,0.02633372,48.12954 +485,0.001377241749877817,0.04007664410346001,0.09608256746148133,209105967926.47348,7837.077135637075,976.5186263375748,159721000.0,0.01391632,50.44996 +486,0.0010065912062420452,0.03462262936938009,0.08547732900553352,207802854039.96924,7687.5437526926125,861.8140177862728,248125000.0,0.02519517,44.22858 +487,0.0013455117618498289,0.04647724185952424,0.07887724232057339,194360379654.50916,8071.000609963486,945.9910204046719,156341000.0,0.01272237,53.9564 +488,0.0017736779086343602,0.03957173080516785,0.09663520073323317,221458714701.1301,8015.315999896439,1138.494224382263,149913000.0,0.01248343,50.25926 +489,0.0016272696305939579,0.038022897837344485,0.0682724564457589,215325465397.95343,7635.436864456415,718.1417113394333,145007000.0,0.01301236,47.44327 +490,0.0016566663334286191,0.04577101550573383,0.07357427327177045,206933987965.33936,7753.755372867241,1107.9865758962958,163756000.0,0.01271688,55.21806 +491,0.0019020267481649502,0.039083990638923365,0.07081480992458516,220018194858.27365,7901.716398850394,1192.698067216926,196647000.0,0.0167981,48.21728 +492,0.0011213511886717167,0.036435259370740644,0.06744338452735876,208488729386.14444,7662.846422133052,771.0679735883808,231997000.0,0.02243247,45.33662 +493,0.0014017041061733905,0.042371118280163766,0.09755856971394652,219936453323.83603,7802.842974502666,758.7858290941851,112683000.0,0.008835714,54.67612 +494,0.0012835041060166783,0.044783095277721875,0.09105621902461823,221082114440.45746,7902.899238478879,1294.4893855077933,206905000.0,0.01531541,57.11823 +495,0.0018514856990519387,0.042672454098592195,0.08005436360915492,194810157411.48297,7605.426154697975,734.5800182057566,99885500.0,0.008820299,50.926 +496,0.0015030617331260557,0.043604570758458416,0.09158974543414539,214434858401.0386,7864.494275300546,1103.4165664337388,156818000.0,0.01228314,54.77713 +497,0.0013566921891387579,0.04871443996611599,0.06722270952472058,197848743007.2057,7655.386394564953,769.2661394179344,135960000.0,0.01039036,57.28914 +498,0.001617800980714269,0.0382421749776998,0.09311469177569835,211823109458.38174,7700.939292815398,946.5413348931921,145680000.0,0.01312987,48.57398 +499,0.0011016793237501143,0.0365500253365004,0.07977673078953094,206392468349.54596,7834.690456491862,1008.9412545607665,265945000.0,0.02582412,45.51746 +500,0.0012948739784798273,0.04530297606144245,0.08954759088868602,206155293901.33234,7659.020756760413,1025.0823042074908,162568000.0,0.01276384,56.51965 diff --git a/docs/figures/fea_vs_predicted_deflection.png b/docs/figures/fea_vs_predicted_deflection.png new file mode 100644 index 0000000..3cd7964 Binary files /dev/null and b/docs/figures/fea_vs_predicted_deflection.png differ diff --git a/docs/figures/fea_vs_predicted_frequency.png b/docs/figures/fea_vs_predicted_frequency.png new file mode 100644 index 0000000..c3a2cc5 Binary files /dev/null and b/docs/figures/fea_vs_predicted_frequency.png differ diff --git a/docs/figures/fea_vs_predicted_stress.png b/docs/figures/fea_vs_predicted_stress.png new file mode 100644 index 0000000..8044472 Binary files /dev/null and b/docs/figures/fea_vs_predicted_stress.png differ diff --git a/docs/figures/ml_training_validation_loss.png b/docs/figures/ml_training_validation_loss.png new file mode 100644 index 0000000..b0a6258 Binary files /dev/null and b/docs/figures/ml_training_validation_loss.png differ diff --git a/docs/figures/prediction_error_distribution.png b/docs/figures/prediction_error_distribution.png new file mode 100644 index 0000000..4575dce Binary files /dev/null and b/docs/figures/prediction_error_distribution.png differ diff --git a/docs/validation/ml_test_metrics.csv b/docs/validation/ml_test_metrics.csv new file mode 100644 index 0000000..14a6e98 --- /dev/null +++ b/docs/validation/ml_test_metrics.csv @@ -0,0 +1,4 @@ +response,mae,rmse,unit,mape_percent +max_stress,1.9490350221438695,2.932099727218547,MPa,1.0589989299689422 +tip_deflection,0.18823362033881488,0.23935947693489001,mm,1.2683177190784598 +mode_1_frequency,0.18998264970865583,0.23496431869832404,Hz,0.39162889707918835 diff --git a/docs/validation/ml_test_predictions.csv b/docs/validation/ml_test_predictions.csv new file mode 100644 index 0000000..bd56c36 --- /dev/null +++ b/docs/validation/ml_test_predictions.csv @@ -0,0 +1,76 @@ +dataset_index,fea_stress_mpa,predicted_stress_mpa,stress_error_percent,fea_deflection_mm,predicted_deflection_mm,deflection_error_percent,fea_mode_1_hz,predicted_mode_1_hz,frequency_error_percent +42,274.448,272.13121021255654,0.8441634799464595,28.65977,28.626280741528284,0.11685110687112067,40.25603,40.214920003300534,0.10212133859068655 +122,149.726,150.37295949221573,0.4320956228148236,12.55155,12.55032835153552,0.009733048623324862,51.24648,51.448913689454734,0.3950196958985984 +17,129.528,128.30799269712094,0.9418869301456559,9.737117999999999,9.813152064206239,0.7808682631374104,58.3487,58.48410556354259,0.23206269127262613 +167,141.19,139.708068538925,1.0496008648452495,11.12311,11.222740169858184,0.8957042576957763,56.66618,56.65204309250483,0.024947698071696476 +297,106.189,104.71795467386846,1.385308578225181,7.358541,7.087176196197982,3.6877528276599616,58.67287,58.99222490194318,0.5442973932299178 +309,197.111,200.4860308954336,1.712248882829271,16.78299,17.30556942066072,3.1137444559087517,50.81534,50.28339202023226,1.0468255841006593 +76,148.075,146.46737102704878,1.085685614013982,12.96576,12.723348811011167,1.8696257603783577,48.61301,48.78898020054705,0.36198170108587946 +313,130.024,129.76400711091839,0.19995761481082502,14.580070000000001,14.569931488331855,0.06953678321259889,40.96967,41.08347741534639,0.27778455463855617 +296,136.049,134.58927938411748,1.072937409229407,9.990808,9.92060088733867,0.7027170641386475,59.21533,59.284097706745854,0.11613159421023628 +208,139.017,137.5602948458969,1.0478611638167277,13.25902,13.219076662222411,0.30125407290725686,46.81396,47.244874046743355,0.920481939027063 +325,128.214,126.38690881913325,1.4250325088264526,9.70662,9.59750402573519,1.124139754773625,57.60937,58.05981301578479,0.7818919314423887 +31,339.243,342.5390047921197,0.9715763603433945,30.927889999999998,31.306249410860353,1.2233599216123519,46.23214,46.473090696943764,0.5211757382283471 +129,178.66,177.02560240987148,0.9148089052549598,15.42724,15.378379174143106,0.31671786954046577,51.32497,51.268952656498335,0.10914247685223215 +440,334.329,334.71227046839505,0.1146387146777738,37.458909999999996,37.31760874886367,0.3772166652375374,40.7236,40.407508768589295,0.7761868582608176 +105,188.075,184.43881588802657,1.9333691941903148,19.92095,19.85370361024632,0.33756617909126163,39.75445,39.89926561798853,0.364275239598421 +290,227.159,225.35985347308852,0.7920207990488896,18.30562,18.276313023749207,0.16009824442327142,51.1127,51.17278931461073,0.11756239566826894 +267,144.223,148.021632341125,2.633860300454859,14.1373,14.597671530649645,3.2564317843551813,44.58579,44.147606211195814,0.9827879887385389 +398,192.78,191.28761673931072,0.7741380125994856,19.87259,20.00370905137204,0.659798503225,41.8462,41.60971485283626,0.5651293239618909 +212,166.386,166.63320642852736,0.1485740558264248,16.3791,16.108387169762878,1.652794294174414,44.56947,44.7315990745063,0.36376711346645235 +43,132.889,132.00803037314097,0.6629364558834999,9.59169,9.22767730513522,3.79508402445013,56.1129,56.36174203539302,0.4434667169100476 +467,203.259,204.0166041077326,0.3727284438733803,21.00174,21.112740201313805,0.5285285948392962,42.50944,42.7806186921512,0.6379258163626783 +160,160.414,165.50133161337092,3.171376322123334,11.335059999999999,11.535777697680269,1.7707687271198302,59.76948,59.668240997687164,0.16938243784760643 +317,138.883,136.12888755105746,1.9830450443485106,15.559140000000001,15.481741214504785,0.497448994579489,40.76916,40.62381313063999,0.35651180784694 +57,142.15,143.4712291402295,0.9294612312553593,11.26476,11.441895068427597,1.572470859810569,52.98613,52.79743752880044,0.3561167256404008 +279,80.4094,78.60520396645228,2.2437625868962017,5.586346,5.414545443461058,3.0753654811023567,59.48575,59.465718342157906,0.03367471678863712 +23,190.176,191.21617946944917,0.5469562244705759,21.65979,21.939008886378254,1.289111696735071,40.07667,39.97532189563701,0.25288554254380646 +420,130.566,130.57534841791866,0.007159917527267663,10.83774,11.05890548975869,2.040697504818247,50.92668,50.71804841833739,0.4096704942529305 +272,105.678,104.87871440557325,0.7563405764934529,8.140125,8.172602209571147,0.39897679177098805,53.68218,53.53434735601382,0.27538494894615784 +15,103.329,104.58138080583842,1.2120322521638853,7.48595,7.877220105330684,5.2267261380410535,59.16491,59.26153942042988,0.16332217936252696 +432,209.824,210.9694388991427,0.5459046148880408,20.60915,20.66925922310192,0.2916627959033804,43.58569,43.44131692042306,0.3312396329550752 +316,258.888,250.70323224685114,3.16150912871545,22.667820000000003,22.082232657940576,2.583342121383637,51.59212,51.69800696957759,0.20523864802917519 +109,146.11,146.4827141612869,0.25509147990344283,11.4848,11.632117045764494,1.2827132014880003,55.77594,55.62369676875108,0.2729550254983013 +137,116.673,116.67490821102813,0.00163552066727423,8.614212,8.468071587687763,1.6965035491608043,56.46961,56.44972728490281,0.035209584583972546 +190,130.136,130.58445624766284,0.3446058336377653,10.61867,10.83734485008912,2.0593431200811487,52.82385,52.787310135660256,0.06917304274441204 +287,171.461,169.06678367366754,1.3963620452070442,19.644209999999998,19.336859967046127,1.564583319735791,38.64532,39.18606879267061,1.3992607453389247 +451,213.219,211.696750671673,0.713936998263288,21.31171,21.43337545022351,0.5708854438405516,40.9142,41.05166212824357,0.33597657596523883 +16,137.729,139.3202158944519,1.155323783990239,13.3726,13.350812500058627,0.16292643122035777,44.95145,44.804597487366586,0.32669138066384 +300,92.6734,92.74444870928694,0.0766656983416418,7.632664,7.635477177889538,0.03685709065063151,51.25112,51.30159533531011,0.09848630685555912 +240,134.389,134.50813326943097,0.0886480808927555,11.1082,10.970262963471928,1.2417586695240603,52.40608,52.682274018571086,0.5270266705143425 +110,317.101,318.1433705123675,0.32871877173754954,29.11953,29.304247049517578,0.6343407655191378,45.78276,45.77209752863784,0.023289271686896814 +431,195.477,196.0517089456437,0.2940033587806715,14.94727,15.085671157780881,0.9259293354631326,55.70136,55.790945550867214,0.16083189147843563 +195,122.291,121.51822546269568,0.6319144804640668,11.39212,11.349850084436378,0.37104520987860445,44.87243,45.13852455167049,0.5930023216270911 +34,222.919,220.34743127729507,1.1535888473862332,19.77011,19.55820466504549,1.0718470203479413,51.33286,51.33225664822386,0.001175371440701417 +286,208.407,209.54119465043846,0.544220995666392,15.397070000000001,15.693006799362971,1.9220332138710157,57.29776,57.28324584429875,0.025331104917974745 +314,210.637,223.2833300271023,6.003850238610639,14.91733,15.334447176956685,2.7961919254765113,57.82756,57.936387080017326,0.1881924120909263 +458,192.327,192.9411199876968,0.3193103348447158,19.49838,19.84532327902626,1.7793441251337947,43.52351,43.39471385999859,0.29592314590760854 +35,142.191,144.37484714332217,1.535854690748479,13.4156,13.729455921259426,2.3394847883018755,46.5728,46.343395563782934,0.49257170755691443 +333,196.064,198.11009299387388,1.0435842346753428,15.987029999999999,16.105060770833667,0.7382907946858757,53.21741,53.15539713427091,0.11652740283506413 +182,207.879,205.0143490618039,1.3780376748955367,16.78317,16.523423007165135,1.547663479753018,55.88252,56.436428223544944,0.9912012263314972 +49,219.118,220.73533113141198,0.738109663018086,24.312150000000003,24.690950771651636,1.5580718762085357,40.79314,40.55618942763107,0.5808588708026224 +6,269.621,272.28623215678294,0.988510597016896,27.24539,27.430883934694943,0.6808268653704034,43.87272,44.09748970156992,0.5123222393549313 +102,282.157,294.0323087024773,4.208759202315475,22.96308,23.69546823627052,3.1894163860881077,53.47129,53.05083402426985,0.7863209878238522 +175,157.912,155.95812545519587,1.237318598209209,14.383140000000001,14.283375056772945,0.6936242241058346,49.43597,49.332171476145064,0.20996558549358685 +139,171.252,173.85683755258472,1.5210552592581152,16.60204,16.962544777609672,2.17144867504037,43.35496,43.19729048777958,0.3636712205948655 +480,145.159,145.36789473783446,0.1439075343826105,14.7096,14.772963150014558,0.4307605238385706,44.34643,44.760881030543224,0.9345758622356439 +41,174.947,171.77547602598577,1.8128484478237537,14.87771,14.642012777063732,1.5842305229519085,52.43997,52.531335455004545,0.1742286561272674 +366,177.175,173.71115403905495,1.9550421678820602,15.30511,15.172242356885459,0.8681260253244887,49.87935,50.13899321522556,0.5205424995024168 +22,88.7934,88.08407972191466,0.7988434704441277,6.5026839999999995,6.559953894706915,0.8807116370242637,58.65858,58.4011217338076,0.43890981710160937 +275,181.245,181.53888430468845,0.1621475376912211,15.69864,15.79401407635589,0.6075308202232111,50.76481,50.586849282035345,0.3505592121090414 +163,311.733,313.108905044426,0.4413729199109572,35.1699,34.8779923344602,0.8299928789669515,40.51074,40.59294830605589,0.2029296578040529 +320,165.5,166.50066716928754,0.6046327306873309,16.408559999999998,16.37673703199716,0.19394125994506448,43.96808,43.745807448787424,0.5055316293378657 +303,200.948,202.11738011058426,0.5819316990386861,19.799799999999998,19.8606412716678,0.30728225369851386,43.24253,43.278317046805206,0.08275891074181868 +113,104.605,105.20524535424876,0.5738209017243556,7.9322360000000005,8.132726204605714,2.5275370602401854,57.08203,56.99563603441233,0.15135054865370917 +237,158.553,156.50519807068335,1.2915567219268258,12.80154,12.550525737311492,1.9608130169378617,56.85573,57.33666673102755,0.8458896421302613 +396,184.358,179.24355233054473,2.774193509072164,13.472019999999999,13.30089918369176,1.2701941973678785,59.06584,58.718338970125416,0.5883282619439353 +437,230.981,229.18728818868425,0.7765624927226678,19.25119,19.14509642311174,0.5511013962682875,52.46686,52.84333663902092,0.7175513057593341 +491,231.997,232.1199294730686,0.052987527023450706,22.43247,22.4012419211296,0.13920927508384495,45.33662,45.30147994186314,0.07750921470736201 +14,311.583,313.27499583437026,0.543032140511599,32.84355,33.29795345481694,1.3835394006340367,41.40182,41.17748646202045,0.5418446290031547 +141,225.316,223.0130947629142,1.0220779869542416,19.73357,19.458650253397426,1.3931576830881254,50.55017,50.79319102496676,0.48075214181625525 +194,154.633,150.38605108959524,2.7464699710959315,12.03957,11.745071871709811,2.4460851034562547,53.35685,53.482333998997795,0.23517879896919297 +36,205.546,205.32753239157003,0.10628648012122267,18.87922,18.958985697856665,0.42250526164040303,45.91477,45.85656302712764,0.12677178361637295 +97,199.348,198.02348054716083,0.6644257543788581,18.77233,18.55576326150663,1.153648686622111,45.18773,45.35859999609362,0.37813361302641557 +449,238.148,236.60322961255545,0.6486598197106656,23.00238,22.997776400982982,0.020013576929947565,47.17491,46.802399640320125,0.7896366091209752 +461,248.565,247.05387957072725,0.6079377343040014,25.7504,25.822291000318923,0.27918401391404785,42.81855,42.714904810864766,0.24205674674933103 +136,153.611,153.48181621938593,0.08409800119397572,11.40235,11.529262743607545,1.1130402382626692,57.03368,57.22917475817141,0.34277072454629215 diff --git a/pyproject.toml b/pyproject.toml index e514ca7..fed3283 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,6 +31,12 @@ dev = [ "ruff>=0.5", "mypy>=1.10", "types-PyYAML", + "scipy-stubs", + "pandas-stubs", +] + +ml = [ + "torch>=2.7", ] [project.scripts] diff --git a/scripts/evaluate_surrogate.py b/scripts/evaluate_surrogate.py new file mode 100644 index 0000000..ca0a8f8 --- /dev/null +++ b/scripts/evaluate_surrogate.py @@ -0,0 +1,474 @@ +from pathlib import Path + +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd +from numpy.typing import NDArray + +from bodysimpy.ml.dataset import ( + load_surrogate_arrays, +) +from bodysimpy.ml.evaluation import ( + TargetMetrics, + calculate_metrics, + calculate_percentage_errors, + predict, +) +from bodysimpy.ml.training import ( + TrainingResult, + train_surrogate, +) + +DATASET_PATH = Path("data/surrogate/fea_surrogate_dataset.csv") + +FIGURE_DIRECTORY = Path("docs/figures") + +VALIDATION_DIRECTORY = Path("docs/validation") + + +def save_loss_plot( + result: TrainingResult, +) -> None: + """Plot training and validation loss versus epoch.""" + + epochs = np.arange( + 1, + len(result.train_losses) + 1, + ) + + plt.figure(figsize=(8, 5)) + + plt.plot( + epochs, + result.train_losses, + label="Training loss", + ) + + plt.plot( + epochs, + result.validation_losses, + label="Validation loss", + ) + + plt.axvline( + result.best_epoch, + linestyle="--", + label=f"Best epoch: {result.best_epoch}", + ) + + plt.xlabel("Epoch") + plt.ylabel("Normalized MSE") + plt.title("BodySimPy Surrogate Training History") + + plt.legend() + plt.grid(True) + plt.tight_layout() + + plt.savefig( + FIGURE_DIRECTORY / "ml_training_validation_loss.png", + dpi=200, + ) + + plt.close() + + +def save_prediction_scatter( + reference: NDArray[np.float64], + prediction: NDArray[np.float64], + *, + x_label: str, + y_label: str, + title: str, + output_path: Path, +) -> None: + """Create an FEA-versus-surrogate prediction plot.""" + + lower_bound = float( + min( + np.min(reference), + np.min(prediction), + ) + ) + + upper_bound = float( + max( + np.max(reference), + np.max(prediction), + ) + ) + + plt.figure(figsize=(6, 6)) + + plt.scatter( + reference, + prediction, + alpha=0.75, + ) + + plt.plot( + [ + lower_bound, + upper_bound, + ], + [ + lower_bound, + upper_bound, + ], + linestyle="--", + label="Ideal prediction", + ) + + plt.xlabel(x_label) + plt.ylabel(y_label) + plt.title(title) + + plt.legend() + plt.grid(True) + plt.tight_layout() + + plt.savefig( + output_path, + dpi=200, + ) + + plt.close() + + +def save_error_distribution( + stress_errors_percent: NDArray[np.float64], + deflection_errors_percent: NDArray[np.float64], + frequency_errors_percent: NDArray[np.float64], +) -> None: + """Plot percentage-error distributions for all predicted responses.""" + + plt.figure(figsize=(9, 5)) + + plt.hist( + stress_errors_percent, + bins=20, + alpha=0.5, + label="Stress", + ) + + plt.hist( + deflection_errors_percent, + bins=20, + alpha=0.5, + label="Tip deflection", + ) + + plt.hist( + frequency_errors_percent, + bins=20, + alpha=0.5, + label="Mode-1 frequency", + ) + + plt.xlabel("Absolute percentage error [%]") + plt.ylabel("Test samples") + + plt.title("BodySimPy Surrogate Prediction Error Distribution") + + plt.legend() + plt.grid(True) + plt.tight_layout() + + plt.savefig( + FIGURE_DIRECTORY / "prediction_error_distribution.png", + dpi=200, + ) + + plt.close() + + +def save_metrics_csv( + *, + stress_metrics: TargetMetrics, + deflection_metrics: TargetMetrics, + frequency_metrics: TargetMetrics, +) -> None: + """Store held-out test metrics in engineering units.""" + + dataframe = pd.DataFrame( + [ + { + "response": "max_stress", + "mae": stress_metrics.mae / 1e6, + "rmse": stress_metrics.rmse / 1e6, + "unit": "MPa", + "mape_percent": (stress_metrics.mean_absolute_percentage_error), + }, + { + "response": "tip_deflection", + "mae": deflection_metrics.mae * 1000.0, + "rmse": deflection_metrics.rmse * 1000.0, + "unit": "mm", + "mape_percent": (deflection_metrics.mean_absolute_percentage_error), + }, + { + "response": "mode_1_frequency", + "mae": frequency_metrics.mae, + "rmse": frequency_metrics.rmse, + "unit": "Hz", + "mape_percent": (frequency_metrics.mean_absolute_percentage_error), + }, + ] + ) + + dataframe.to_csv( + VALIDATION_DIRECTORY / "ml_test_metrics.csv", + index=False, + ) + + +def save_test_predictions( + *, + reference: NDArray[np.float64], + prediction: NDArray[np.float64], + test_indices: NDArray[np.int64], +) -> None: + """Store every held-out FEA and PyTorch prediction.""" + + stress_error = calculate_percentage_errors( + reference[:, 0], + prediction[:, 0], + ) + + deflection_error = calculate_percentage_errors( + reference[:, 1], + prediction[:, 1], + ) + + frequency_error = calculate_percentage_errors( + reference[:, 2], + prediction[:, 2], + ) + + dataframe = pd.DataFrame( + { + "dataset_index": test_indices, + "fea_stress_mpa": (reference[:, 0] / 1e6), + "predicted_stress_mpa": (prediction[:, 0] / 1e6), + "stress_error_percent": (stress_error), + "fea_deflection_mm": (reference[:, 1] * 1000.0), + "predicted_deflection_mm": (prediction[:, 1] * 1000.0), + "deflection_error_percent": (deflection_error), + "fea_mode_1_hz": (reference[:, 2]), + "predicted_mode_1_hz": (prediction[:, 2]), + "frequency_error_percent": (frequency_error), + } + ) + + dataframe.to_csv( + VALIDATION_DIRECTORY / "ml_test_predictions.csv", + index=False, + ) + + +def print_metrics( + *, + stress_metrics: TargetMetrics, + deflection_metrics: TargetMetrics, + frequency_metrics: TargetMetrics, +) -> None: + """Print held-out test-set metrics.""" + + print() + print("BodySimPy PyTorch Surrogate Evaluation") + print("=" * 78) + + print() + print(f"{'Response':<20}{'MAE':>16}{'RMSE':>16}{'MAPE':>16}") + + print("-" * 78) + + print( + f"{'Stress':<20}" + f"{stress_metrics.mae / 1e6:>12.4f} MPa" + f"{stress_metrics.rmse / 1e6:>12.4f} MPa" + f"{stress_metrics.mean_absolute_percentage_error:>14.3f} %" + ) + + print( + f"{'Tip deflection':<20}" + f"{deflection_metrics.mae * 1000.0:>12.5f} mm" + f"{deflection_metrics.rmse * 1000.0:>12.5f} mm" + f"{deflection_metrics.mean_absolute_percentage_error:>14.3f} %" + ) + + print( + f"{'Mode-1 frequency':<20}" + f"{frequency_metrics.mae:>12.4f} Hz" + f"{frequency_metrics.rmse:>12.4f} Hz" + f"{frequency_metrics.mean_absolute_percentage_error:>14.3f} %" + ) + + +def main() -> None: + FIGURE_DIRECTORY.mkdir( + parents=True, + exist_ok=True, + ) + + VALIDATION_DIRECTORY.mkdir( + parents=True, + exist_ok=True, + ) + + print(f"Loading FEA dataset from {DATASET_PATH}...") + + features, targets = load_surrogate_arrays(DATASET_PATH) + + print(f"Loaded {features.shape[0]} FEA samples.") + + print() + print("Training surrogate using deterministic train/validation/test split...") + + training_result = train_surrogate( + features, + targets, + seed=42, + batch_size=32, + learning_rate=1e-3, + maximum_epochs=1000, + patience=50, + ) + + test_indices = training_result.split.test_indices + + test_features = features[test_indices] + + test_targets = targets[test_indices] + + print() + print(f"Evaluating {len(test_indices)} held-out test samples...") + + predictions = predict( + training_result.model, + test_features, + feature_standardization=(training_result.feature_standardization), + target_standardization=(training_result.target_standardization), + ) + + stress_reference = test_targets[:, 0] + + stress_prediction = predictions[:, 0] + + deflection_reference = test_targets[:, 1] + + deflection_prediction = predictions[:, 1] + + frequency_reference = test_targets[:, 2] + + frequency_prediction = predictions[:, 2] + + stress_metrics = calculate_metrics( + stress_reference, + stress_prediction, + ) + + deflection_metrics = calculate_metrics( + deflection_reference, + deflection_prediction, + ) + + frequency_metrics = calculate_metrics( + frequency_reference, + frequency_prediction, + ) + + stress_errors_percent = calculate_percentage_errors( + stress_reference, + stress_prediction, + ) + + deflection_errors_percent = calculate_percentage_errors( + deflection_reference, + deflection_prediction, + ) + + frequency_errors_percent = calculate_percentage_errors( + frequency_reference, + frequency_prediction, + ) + + save_loss_plot(training_result) + + save_prediction_scatter( + stress_reference / 1e6, + stress_prediction / 1e6, + x_label="CalculiX FEA stress [MPa]", + y_label="PyTorch predicted stress [MPa]", + title=("FEA vs PyTorch — Maximum Stress"), + output_path=(FIGURE_DIRECTORY / "fea_vs_predicted_stress.png"), + ) + + save_prediction_scatter( + deflection_reference * 1000.0, + deflection_prediction * 1000.0, + x_label="CalculiX FEA tip deflection [mm]", + y_label="PyTorch predicted tip deflection [mm]", + title=("FEA vs PyTorch — Tip Deflection"), + output_path=(FIGURE_DIRECTORY / "fea_vs_predicted_deflection.png"), + ) + + save_prediction_scatter( + frequency_reference, + frequency_prediction, + x_label="CalculiX FEA mode-1 frequency [Hz]", + y_label="PyTorch predicted mode-1 frequency [Hz]", + title=("FEA vs PyTorch — Mode-1 Frequency"), + output_path=(FIGURE_DIRECTORY / "fea_vs_predicted_frequency.png"), + ) + + save_error_distribution( + stress_errors_percent, + deflection_errors_percent, + frequency_errors_percent, + ) + + save_metrics_csv( + stress_metrics=stress_metrics, + deflection_metrics=deflection_metrics, + frequency_metrics=frequency_metrics, + ) + + save_test_predictions( + reference=test_targets, + prediction=predictions, + test_indices=test_indices, + ) + + print_metrics( + stress_metrics=stress_metrics, + deflection_metrics=deflection_metrics, + frequency_metrics=frequency_metrics, + ) + + print() + print(f"Best validation epoch: {training_result.best_epoch}") + + print(f"Best normalized validation MSE: {min(training_result.validation_losses):.6f}") + + print() + print("Generated figures:") + + for path in ( + FIGURE_DIRECTORY / "ml_training_validation_loss.png", + FIGURE_DIRECTORY / "fea_vs_predicted_stress.png", + FIGURE_DIRECTORY / "fea_vs_predicted_deflection.png", + FIGURE_DIRECTORY / "fea_vs_predicted_frequency.png", + FIGURE_DIRECTORY / "prediction_error_distribution.png", + ): + print(f" {path}") + + print() + print("Generated validation data:") + + print(" " + str(VALIDATION_DIRECTORY / "ml_test_metrics.csv")) + + print(" " + str(VALIDATION_DIRECTORY / "ml_test_predictions.csv")) + + +if __name__ == "__main__": + main() diff --git a/scripts/generate_surrogate_dataset.py b/scripts/generate_surrogate_dataset.py new file mode 100644 index 0000000..0f1a6a7 --- /dev/null +++ b/scripts/generate_surrogate_dataset.py @@ -0,0 +1,47 @@ +from pathlib import Path + +from bodysimpy.config.loader import load_config +from bodysimpy.ml.dataset_generation import ( + generate_fea_dataset, + write_fea_dataset, +) +from bodysimpy.ml.design_space import generate_design_points +from bodysimpy.modeling.crossmember import build_crossmember_model + +SAMPLE_COUNT = 500 +RANDOM_SEED = 42 +MAX_WORKERS = 4 + + +def main() -> None: + config = load_config("configs/baseline_crossmember.yaml") + + model = build_crossmember_model(config) + + designs = generate_design_points( + sample_count=SAMPLE_COUNT, + seed=RANDOM_SEED, + ) + + print(f"Generated {len(designs)} Latin Hypercube design points.") + + samples = generate_fea_dataset( + model, + designs, + max_workers=MAX_WORKERS, + ) + + output_path = Path("data/surrogate/fea_surrogate_dataset.csv") + + write_fea_dataset( + samples, + output_path, + ) + + print() + print(f"Saved {len(samples)} FEA samples to:") + print(output_path) + + +if __name__ == "__main__": + main() diff --git a/scripts/train_surrogate.py b/scripts/train_surrogate.py new file mode 100644 index 0000000..5093d47 --- /dev/null +++ b/scripts/train_surrogate.py @@ -0,0 +1,44 @@ +from pathlib import Path + +from bodysimpy.ml.dataset import ( + load_surrogate_arrays, +) +from bodysimpy.ml.training import ( + save_training_checkpoint, + train_surrogate, +) + + +def main() -> None: + dataset_path = Path("data/surrogate/fea_surrogate_dataset.csv") + + features, targets = load_surrogate_arrays(dataset_path) + + result = train_surrogate( + features, + targets, + seed=42, + batch_size=32, + learning_rate=1e-3, + maximum_epochs=1000, + patience=50, + ) + + save_training_checkpoint( + result, + "models/checkpoints/structural_surrogate.pt", + ) + + print() + print("BodySimPy PyTorch Surrogate") + print("=" * 55) + print(f"Dataset samples: {features.shape[0]}") + print(f"Training samples: {len(result.split.train_indices)}") + print(f"Validation samples: {len(result.split.validation_indices)}") + print(f"Test samples: {len(result.split.test_indices)}") + print(f"Best epoch: {result.best_epoch}") + print(f"Final stored validation loss: {min(result.validation_losses):.6f}") + + +if __name__ == "__main__": + main() diff --git a/src/bodysimpy/ml/dataset.py b/src/bodysimpy/ml/dataset.py new file mode 100644 index 0000000..0ea4bca --- /dev/null +++ b/src/bodysimpy/ml/dataset.py @@ -0,0 +1,115 @@ +from dataclasses import dataclass +from pathlib import Path + +import numpy as np +import pandas as pd +import torch +from numpy.typing import NDArray +from torch import Tensor +from torch.utils.data import Dataset + +FEATURE_COLUMNS = ( + "thickness_m", + "height_m", + "width_m", + "youngs_modulus_pa", + "density_kg_m3", + "tip_force_n", +) + +TARGET_COLUMNS = ( + "max_stress_pa", + "tip_deflection_m", + "mode_1_frequency_hz", +) + + +@dataclass(frozen=True, slots=True) +class Standardization: + mean: NDArray[np.float64] + standard_deviation: NDArray[np.float64] + + +class StructuralSurrogateDataset(Dataset[tuple[Tensor, Tensor]]): + """PyTorch dataset for structural surrogate modelling.""" + + def __init__( + self, + features: NDArray[np.float64], + targets: NDArray[np.float64], + ) -> None: + if features.shape[0] != targets.shape[0]: + raise ValueError("Feature and target row counts must match.") + + self._features = torch.tensor( + features, + dtype=torch.float32, + ) + + self._targets = torch.tensor( + targets, + dtype=torch.float32, + ) + + def __len__(self) -> int: + return self._features.shape[0] + + def __getitem__( + self, + index: int, + ) -> tuple[Tensor, Tensor]: + return ( + self._features[index], + self._targets[index], + ) + + +def load_surrogate_arrays( + path: str | Path, +) -> tuple[ + NDArray[np.float64], + NDArray[np.float64], +]: + dataframe = pd.read_csv(path) + + features = dataframe[list(FEATURE_COLUMNS)].to_numpy(dtype=np.float64) + + targets = dataframe[list(TARGET_COLUMNS)].to_numpy(dtype=np.float64) + + return features, targets + + +def fit_standardization( + values: NDArray[np.float64], +) -> Standardization: + mean = np.mean( + values, + axis=0, + ) + + standard_deviation = np.std( + values, + axis=0, + ) + + if np.any(standard_deviation == 0.0): + raise ValueError("Cannot standardize a constant column.") + + return Standardization( + mean=mean, + standard_deviation=standard_deviation, + ) + + +def standardize( + values: NDArray[np.float64], + statistics: Standardization, +) -> NDArray[np.float64]: + return (values - statistics.mean) / statistics.standard_deviation + + +def inverse_standardize( + values: NDArray[np.float64], + statistics: Standardization, +) -> NDArray[np.float64]: + return values * statistics.standard_deviation + statistics.mean diff --git a/src/bodysimpy/ml/dataset_generation.py b/src/bodysimpy/ml/dataset_generation.py new file mode 100644 index 0000000..a30f8d9 --- /dev/null +++ b/src/bodysimpy/ml/dataset_generation.py @@ -0,0 +1,157 @@ +import csv +from concurrent.futures import ThreadPoolExecutor +from dataclasses import dataclass, replace +from pathlib import Path + +from bodysimpy.domain.structural_model import StructuralModel +from bodysimpy.ml.design_space import SurrogateDesignPoint +from bodysimpy.solvers.calculix import CalculiXSolver + + +@dataclass(frozen=True, slots=True) +class SurrogateFeaSample: + sample_index: int + + thickness_m: float + height_m: float + width_m: float + youngs_modulus_pa: float + density_kg_m3: float + tip_force_n: float + + max_stress_pa: float + tip_deflection_m: float + mode_1_frequency_hz: float + + +def evaluate_design_point( + base_model: StructuralModel, + design: SurrogateDesignPoint, +) -> SurrogateFeaSample: + """Evaluate one surrogate-design point with CalculiX.""" + + section = replace( + base_model.section, + thickness_m=design.thickness_m, + height_m=design.height_m, + width_m=design.width_m, + ) + + material = replace( + base_model.material, + youngs_modulus_pa=design.youngs_modulus_pa, + density_kg_m3=design.density_kg_m3, + ) + + model = replace( + base_model, + name=(f"{base_model.name}_ml_{design.sample_index:04d}"), + section=section, + material=material, + tip_force_n=design.tip_force_n, + ) + + solver = CalculiXSolver() + + static_result = solver.run(model) + + modal_result = solver.run_modal( + model, + modes=1, + ) + + if static_result.max_axial_stress_pa is None: + raise ValueError("Static FEA returned no axial stress.") + + return SurrogateFeaSample( + sample_index=design.sample_index, + thickness_m=design.thickness_m, + height_m=design.height_m, + width_m=design.width_m, + youngs_modulus_pa=design.youngs_modulus_pa, + density_kg_m3=design.density_kg_m3, + tip_force_n=design.tip_force_n, + max_stress_pa=static_result.max_axial_stress_pa, + tip_deflection_m=static_result.tip_deflection_m, + mode_1_frequency_hz=(modal_result.natural_frequencies_hz[0]), + ) + + +def generate_fea_dataset( + base_model: StructuralModel, + designs: tuple[SurrogateDesignPoint, ...], + *, + max_workers: int = 4, +) -> tuple[SurrogateFeaSample, ...]: + """Evaluate a complete surrogate training design.""" + + if max_workers <= 0: + raise ValueError("Maximum worker count must be positive.") + + def evaluate( + design: SurrogateDesignPoint, + ) -> SurrogateFeaSample: + return evaluate_design_point( + base_model, + design, + ) + + with ThreadPoolExecutor(max_workers=max_workers) as executor: + return tuple( + executor.map( + evaluate, + designs, + ) + ) + + +def write_fea_dataset( + samples: tuple[SurrogateFeaSample, ...], + path: str | Path, +) -> None: + """Write the generated surrogate dataset to CSV.""" + + output_path = Path(path) + + output_path.parent.mkdir( + parents=True, + exist_ok=True, + ) + + with output_path.open( + "w", + encoding="utf-8", + newline="", + ) as stream: + writer = csv.writer(stream) + + writer.writerow( + [ + "sample", + "thickness_m", + "height_m", + "width_m", + "youngs_modulus_pa", + "density_kg_m3", + "tip_force_n", + "max_stress_pa", + "tip_deflection_m", + "mode_1_frequency_hz", + ] + ) + + for sample in samples: + writer.writerow( + [ + sample.sample_index, + sample.thickness_m, + sample.height_m, + sample.width_m, + sample.youngs_modulus_pa, + sample.density_kg_m3, + sample.tip_force_n, + sample.max_stress_pa, + sample.tip_deflection_m, + sample.mode_1_frequency_hz, + ] + ) diff --git a/src/bodysimpy/ml/design_space.py b/src/bodysimpy/ml/design_space.py new file mode 100644 index 0000000..18a8be7 --- /dev/null +++ b/src/bodysimpy/ml/design_space.py @@ -0,0 +1,76 @@ +from dataclasses import dataclass + +import numpy as np +from scipy.stats import qmc + + +@dataclass(frozen=True, slots=True) +class SurrogateDesignPoint: + sample_index: int + thickness_m: float + height_m: float + width_m: float + youngs_modulus_pa: float + density_kg_m3: float + tip_force_n: float + + +def generate_design_points( + *, + sample_count: int, + seed: int, +) -> tuple[SurrogateDesignPoint, ...]: + """Generate a six-dimensional Latin Hypercube design.""" + + if sample_count <= 0: + raise ValueError("Surrogate sample count must be positive.") + + lower_bounds = np.array( + [ + 0.0010, + 0.032, + 0.060, + 190e9, + 7600.0, + 700.0, + ], + dtype=float, + ) + + upper_bounds = np.array( + [ + 0.0020, + 0.050, + 0.100, + 225e9, + 8100.0, + 1300.0, + ], + dtype=float, + ) + + sampler = qmc.LatinHypercube( + d=6, + seed=seed, + ) + + unit_sample = sampler.random(n=sample_count) + + scaled_sample = qmc.scale( + unit_sample, + lower_bounds, + upper_bounds, + ) + + return tuple( + SurrogateDesignPoint( + sample_index=index + 1, + thickness_m=float(row[0]), + height_m=float(row[1]), + width_m=float(row[2]), + youngs_modulus_pa=float(row[3]), + density_kg_m3=float(row[4]), + tip_force_n=float(row[5]), + ) + for index, row in enumerate(scaled_sample) + ) diff --git a/src/bodysimpy/ml/evaluation.py b/src/bodysimpy/ml/evaluation.py new file mode 100644 index 0000000..9970285 --- /dev/null +++ b/src/bodysimpy/ml/evaluation.py @@ -0,0 +1,96 @@ +from dataclasses import dataclass + +import numpy as np +import torch +from numpy.typing import NDArray + +from bodysimpy.ml.dataset import ( + Standardization, + inverse_standardize, + standardize, +) +from bodysimpy.ml.model import StructuralSurrogate + + +@dataclass(frozen=True, slots=True) +class TargetMetrics: + """Regression metrics for one structural response.""" + + mae: float + rmse: float + mean_absolute_percentage_error: float + + +def predict( + model: StructuralSurrogate, + features: NDArray[np.float64], + *, + feature_standardization: Standardization, + target_standardization: Standardization, +) -> NDArray[np.float64]: + """Predict structural responses in physical units.""" + + normalized_features = standardize( + features, + feature_standardization, + ) + + feature_tensor = torch.tensor( + normalized_features, + dtype=torch.float32, + ) + + model.eval() + + with torch.no_grad(): + normalized_predictions = model(feature_tensor).cpu().numpy().astype(np.float64) + + return inverse_standardize( + normalized_predictions, + target_standardization, + ) + + +def calculate_metrics( + reference: NDArray[np.float64], + prediction: NDArray[np.float64], +) -> TargetMetrics: + """Calculate MAE, RMSE and MAPE.""" + + if reference.shape != prediction.shape: + raise ValueError("Reference and prediction arrays must have matching shapes.") + + if reference.size == 0: + raise ValueError("At least one prediction is required.") + + if np.any(reference == 0.0): + raise ValueError("MAPE cannot be calculated with zero reference values.") + + errors = prediction - reference + + mae = float(np.mean(np.abs(errors))) + + rmse = float(np.sqrt(np.mean(errors**2))) + + percentage_errors = np.abs(errors) / np.abs(reference) * 100.0 + + return TargetMetrics( + mae=mae, + rmse=rmse, + mean_absolute_percentage_error=float(np.mean(percentage_errors)), + ) + + +def calculate_percentage_errors( + reference: NDArray[np.float64], + prediction: NDArray[np.float64], +) -> NDArray[np.float64]: + """Return absolute percentage error for each prediction.""" + + if reference.shape != prediction.shape: + raise ValueError("Reference and prediction arrays must have matching shapes.") + + if np.any(reference == 0.0): + raise ValueError("Percentage error cannot use zero reference values.") + + return np.abs(prediction - reference) / np.abs(reference) * 100.0 diff --git a/src/bodysimpy/ml/model.py b/src/bodysimpy/ml/model.py new file mode 100644 index 0000000..8e8dba6 --- /dev/null +++ b/src/bodysimpy/ml/model.py @@ -0,0 +1,29 @@ +from typing import cast + +from torch import Tensor, nn + + +class StructuralSurrogate(nn.Module): + """MLP surrogate for structural FEA responses.""" + + def __init__(self) -> None: + super().__init__() + + self.network = nn.Sequential( + nn.Linear(6, 64), + nn.ReLU(), + nn.Linear(64, 64), + nn.ReLU(), + nn.Linear(64, 32), + nn.ReLU(), + nn.Linear(32, 3), + ) + + def forward( + self, + features: Tensor, + ) -> Tensor: + return cast( + Tensor, + self.network(features), + ) \ No newline at end of file diff --git a/src/bodysimpy/ml/training.py b/src/bodysimpy/ml/training.py new file mode 100644 index 0000000..818322c --- /dev/null +++ b/src/bodysimpy/ml/training.py @@ -0,0 +1,261 @@ +from copy import deepcopy +from dataclasses import dataclass +from pathlib import Path + +import numpy as np +import torch +from numpy.typing import NDArray +from torch import Tensor, nn +from torch.optim import Adam +from torch.utils.data import DataLoader + +from bodysimpy.ml.dataset import ( + Standardization, + StructuralSurrogateDataset, + fit_standardization, + standardize, +) +from bodysimpy.ml.model import StructuralSurrogate + + +@dataclass(frozen=True, slots=True) +class DataSplit: + train_indices: NDArray[np.int64] + validation_indices: NDArray[np.int64] + test_indices: NDArray[np.int64] + + +@dataclass(frozen=True, slots=True) +class TrainingResult: + model: StructuralSurrogate + feature_standardization: Standardization + target_standardization: Standardization + train_losses: tuple[float, ...] + validation_losses: tuple[float, ...] + best_epoch: int + split: DataSplit + + +def create_data_split( + sample_count: int, + *, + seed: int, +) -> DataSplit: + if sample_count < 10: + raise ValueError("At least ten samples are required.") + + rng = np.random.default_rng(seed) + + indices = rng.permutation(sample_count) + + train_end = int(0.70 * sample_count) + + validation_end = int(0.85 * sample_count) + + return DataSplit( + train_indices=indices[:train_end], + validation_indices=indices[train_end:validation_end], + test_indices=indices[validation_end:], + ) + + +def _mean_loss( + model: StructuralSurrogate, + loader: DataLoader[tuple[Tensor, Tensor]], + loss_function: nn.Module, + device: torch.device, +) -> float: + model.eval() + + losses: list[float] = [] + + with torch.no_grad(): + for features, targets in loader: + features = features.to(device) + targets = targets.to(device) + + predictions = model(features) + + loss = loss_function( + predictions, + targets, + ) + + losses.append(float(loss.item())) + + return float(np.mean(losses)) + + +def train_surrogate( + features: NDArray[np.float64], + targets: NDArray[np.float64], + *, + seed: int = 42, + batch_size: int = 32, + learning_rate: float = 1e-3, + maximum_epochs: int = 1000, + patience: int = 50, +) -> TrainingResult: + """Train the BodySimPy structural surrogate.""" + + torch.manual_seed(seed) + + split = create_data_split( + features.shape[0], + seed=seed, + ) + + train_features = features[split.train_indices] + + train_targets = targets[split.train_indices] + + feature_statistics = fit_standardization(train_features) + + target_statistics = fit_standardization(train_targets) + + normalized_features = standardize( + features, + feature_statistics, + ) + + normalized_targets = standardize( + targets, + target_statistics, + ) + + train_dataset = StructuralSurrogateDataset( + normalized_features[split.train_indices], + normalized_targets[split.train_indices], + ) + + validation_dataset = StructuralSurrogateDataset( + normalized_features[split.validation_indices], + normalized_targets[split.validation_indices], + ) + + training_loader = DataLoader( + train_dataset, + batch_size=batch_size, + shuffle=True, + ) + + validation_loader = DataLoader( + validation_dataset, + batch_size=batch_size, + shuffle=False, + ) + + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + + model = StructuralSurrogate().to(device) + + optimizer = Adam( + model.parameters(), + lr=learning_rate, + ) + + loss_function = nn.MSELoss() + + train_losses: list[float] = [] + validation_losses: list[float] = [] + + best_validation_loss = float("inf") + best_model_state = deepcopy(model.state_dict()) + + best_epoch = 0 + epochs_without_improvement = 0 + + for epoch in range( + 1, + maximum_epochs + 1, + ): + model.train() + + batch_losses: list[float] = [] + + for features_batch, targets_batch in training_loader: + features_batch = features_batch.to(device) + + targets_batch = targets_batch.to(device) + + optimizer.zero_grad() + + predictions = model(features_batch) + + loss = loss_function( + predictions, + targets_batch, + ) + + loss.backward() + + optimizer.step() + + batch_losses.append(float(loss.item())) + + training_loss = float(np.mean(batch_losses)) + + validation_loss = _mean_loss( + model, + validation_loader, + loss_function, + device, + ) + + train_losses.append(training_loss) + + validation_losses.append(validation_loss) + + if validation_loss < best_validation_loss: + best_validation_loss = validation_loss + + best_model_state = deepcopy(model.state_dict()) + + best_epoch = epoch + epochs_without_improvement = 0 + else: + epochs_without_improvement += 1 + + if epochs_without_improvement >= patience: + break + + model.load_state_dict(best_model_state) + + model.to("cpu") + model.eval() + + return TrainingResult( + model=model, + feature_standardization=(feature_statistics), + target_standardization=(target_statistics), + train_losses=tuple(train_losses), + validation_losses=tuple(validation_losses), + best_epoch=best_epoch, + split=split, + ) + + +def save_training_checkpoint( + result: TrainingResult, + path: str | Path, +) -> None: + """Save trained model and preprocessing metadata.""" + + checkpoint_path = Path(path) + + checkpoint_path.parent.mkdir( + parents=True, + exist_ok=True, + ) + + torch.save( + { + "model_state_dict": (result.model.state_dict()), + "feature_mean": (result.feature_standardization.mean), + "feature_standard_deviation": (result.feature_standardization.standard_deviation), + "target_mean": (result.target_standardization.mean), + "target_standard_deviation": (result.target_standardization.standard_deviation), + "best_epoch": (result.best_epoch), + }, + checkpoint_path, + ) diff --git a/tests/integration/test_ml_dataset_generation.py b/tests/integration/test_ml_dataset_generation.py new file mode 100644 index 0000000..983a81a --- /dev/null +++ b/tests/integration/test_ml_dataset_generation.py @@ -0,0 +1,36 @@ +import shutil + +import pytest + +from bodysimpy.config.loader import load_config +from bodysimpy.ml.dataset_generation import generate_fea_dataset +from bodysimpy.ml.design_space import generate_design_points +from bodysimpy.modeling.crossmember import build_crossmember_model + + +@pytest.mark.skipif( + shutil.which("ccx") is None, + reason="CalculiX is not installed.", +) +def test_generate_small_fea_surrogate_dataset() -> None: + config = load_config("configs/baseline_crossmember.yaml") + + model = build_crossmember_model(config) + + designs = generate_design_points( + sample_count=2, + seed=42, + ) + + samples = generate_fea_dataset( + model, + designs, + max_workers=2, + ) + + assert len(samples) == 2 + + for sample in samples: + assert sample.max_stress_pa > 0.0 + assert sample.tip_deflection_m > 0.0 + assert sample.mode_1_frequency_hz > 0.0 diff --git a/tests/unit/test_ml_dataset.py b/tests/unit/test_ml_dataset.py new file mode 100644 index 0000000..ff6297a --- /dev/null +++ b/tests/unit/test_ml_dataset.py @@ -0,0 +1,57 @@ +import numpy as np +import pytest + +from bodysimpy.ml.dataset import ( + StructuralSurrogateDataset, + fit_standardization, + inverse_standardize, + standardize, +) + + +def test_surrogate_dataset_shapes() -> None: + features = np.ones( + (10, 6), + dtype=np.float64, + ) + + targets = np.ones( + (10, 3), + dtype=np.float64, + ) + + dataset = StructuralSurrogateDataset( + features, + targets, + ) + + features_row, targets_row = dataset[0] + + assert len(dataset) == 10 + assert tuple(features_row.shape) == (6,) + assert tuple(targets_row.shape) == (3,) + + +def test_standardization_round_trip() -> None: + values = np.array( + [ + [1.0, 10.0], + [2.0, 20.0], + [3.0, 30.0], + ], + dtype=np.float64, + ) + + statistics = fit_standardization(values) + + normalized = standardize( + values, + statistics, + ) + + recovered = inverse_standardize( + normalized, + statistics, + ) + + assert recovered == pytest.approx(values) diff --git a/tests/unit/test_ml_design_space.py b/tests/unit/test_ml_design_space.py new file mode 100644 index 0000000..3bb9ddf --- /dev/null +++ b/tests/unit/test_ml_design_space.py @@ -0,0 +1,42 @@ +import pytest + +from bodysimpy.ml.design_space import generate_design_points + + +def test_generate_design_points() -> None: + points = generate_design_points( + sample_count=10, + seed=42, + ) + + assert len(points) == 10 + + for point in points: + assert 0.0010 <= point.thickness_m <= 0.0020 + assert 0.032 <= point.height_m <= 0.050 + assert 0.060 <= point.width_m <= 0.100 + assert 190e9 <= point.youngs_modulus_pa <= 225e9 + assert 7600.0 <= point.density_kg_m3 <= 8100.0 + assert 700.0 <= point.tip_force_n <= 1300.0 + + +def test_design_generation_is_reproducible() -> None: + first = generate_design_points( + sample_count=5, + seed=42, + ) + + second = generate_design_points( + sample_count=5, + seed=42, + ) + + assert first == second + + +def test_design_generation_rejects_invalid_sample_count() -> None: + with pytest.raises(ValueError): + generate_design_points( + sample_count=0, + seed=42, + ) diff --git a/tests/unit/test_ml_model.py b/tests/unit/test_ml_model.py new file mode 100644 index 0000000..0d6c556 --- /dev/null +++ b/tests/unit/test_ml_model.py @@ -0,0 +1,19 @@ +import torch + +from bodysimpy.ml.model import StructuralSurrogate + + +def test_surrogate_model_output_shape() -> None: + model = StructuralSurrogate() + + batch = torch.randn( + 8, + 6, + ) + + output = model(batch) + + assert tuple(output.shape) == ( + 8, + 3, + )