diff --git a/.gitignore b/.gitignore index 64d49ae..e13071d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,10 @@ +data/ +logs/ +models_checkpoints/ +predictions_plots/ + + + # Byte-compiled / optimized / DLL files __pycache__/ *.py[codz] @@ -5,6 +12,7 @@ __pycache__/ # C extensions *.so +!matlabfiles/codegen/lib/quadcopter_model/libquadcopter_model.so # Distribution / packaging .Python @@ -14,7 +22,6 @@ dist/ downloads/ eggs/ .eggs/ -lib/ lib64/ parts/ sdist/ @@ -213,4 +220,12 @@ marimo/_lsp/ __marimo__/ # Streamlit -.streamlit/secrets.toml \ No newline at end of file +.streamlit/secrets.toml + + +**.ckpt +models/accel_vs_voltage +logs +reports + +*.prof \ No newline at end of file diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..c70edfa --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.11.13 diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..8592fa8 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,37 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Python: Current File", + "type": "debugpy", + "request": "launch", + "program": "${file}", + "console": "integratedTerminal", + "justMyCode": true + }, + { + "name": "Python: run_optim.py", + "type": "debugpy", + "request": "launch", + "program": "${workspaceFolder}/run_optim.py", + "console": "integratedTerminal", + "justMyCode": true + }, + { + "name": "Python: run_model.py", + "type": "debugpy", + "request": "launch", + "program": "${workspaceFolder}/run_model.py", + "console": "integratedTerminal", + "justMyCode": true + }, + { + "name": "Python: main.py", + "type": "debugpy", + "request": "launch", + "program": "${workspaceFolder}/main.py", + "console": "integratedTerminal", + "justMyCode": true + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..7e68766 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python-envs.pythonProjects": [] +} \ No newline at end of file diff --git a/src/__init__.py b/README.md similarity index 100% rename from src/__init__.py rename to README.md diff --git a/config.yaml b/config.yaml index e959e6c..10188b2 100644 --- a/config.yaml +++ b/config.yaml @@ -1,9 +1,71 @@ scenarios: + # - name: "control_4_motors" + # data: + # input_file: "data/control_4_motors/_inputs.csv" + # states_file: "data/control_4_motors/_states.csv" + # derivatives_file: "data/control_4_motors/_derivatives.csv" + # output_file: "data/control_4_motors/out.csv" + + # #processed_file: "data/processed/accel_vs_voltage.npz" + # input_dim: 4 + # states_dim: 12 + # derivatives_dim : 12 + # output_dim : 12 + # #use_simulink: false + # #simulink_model: "UAV_model.slx" + # #extra_simulink_samples: 0 + # preprocessing: + # outlier_method: "iqr" # options: iqr, zscore + # normalize: true + # normalization_scope: "per_feature" # options: per_feature, global + # fill_method: "linear" # options: linear, cubic, nearest + # augmentation: + # noise_std: 0.01 + # window_size: 50 + # window_stride: 10 + # training: + # models: ["gru"] + # epochs: 1000 + # batch_size: 128 + # lr: 0.1 + # val_split: 0.2 + # test_split: 0.1 + # seed: 42 + # early_stopping_patience: 10 + # model_params: + # gru: + # hidden_dim: 16 + # num_layers: 2 + # dropout: 0.0 + # rnn: + # hidden_dim: 16 + # num_layers: 1 + # dropout: 0.0 + # scheduler: + # type: "ReduceLROnPlateau" + # params: + # patience: 10 + # factor: 0.5 + # min_lr: 0.000001 + # control: + # methods: ["mpc"] + # horizon: 10 + # rl_algorithm: "ppo" # options: ppo, ddpg + # mpc_weight_tracking: 1.0 + # mpc_weight_smoothness: 0.1 + # mpc_weight_effort: 0.01 + # reporting: + # generate_pdf: true + # include_plots: true + # metrics_file: "results/control_4_motors/metrics.json" + - name: "accel_vs_voltage" data: input_file: "data/accel_vs_voltage/in.csv" output_file: "data/accel_vs_voltage/out.csv" processed_file: "data/processed/accel_vs_voltage.npz" + input_dim: 1 + output_dim: 1 use_simulink: false simulink_model: "UAV_model.slx" extra_simulink_samples: 0 @@ -16,15 +78,34 @@ scenarios: noise_std: 0.01 window_size: 50 window_stride: 10 - simulink_noise_samples: 0 training: - models: ["linear", "gru"] - epochs: 50 - batch_size: 32 - lr: 0.001 + models: ["gru"] + epochs: 1000 + batch_size: 128 + lr: 0.01 val_split: 0.2 test_split: 0.1 seed: 42 + early_stopping_patience: 10 + model_params: + gru: + hidden_dim: 32 + num_layers: 2 + dropout: 0.0 + lstm: + hidden_dim: 16 + num_layers: 1 + dropout: 0.0 + rnn: + hidden_dim: 8 + num_layers: 1 + dropout: 0.0 + scheduler: + type: "ReduceLROnPlateau" + params: + patience: 10 + factor: 0.5 + min_lr: 0.000001 control: methods: ["mpc"] horizon: 10 diff --git a/logs/accel_vs_voltage/linear/version_0/hparams.yaml b/logs/accel_vs_voltage/linear/version_0/hparams.yaml new file mode 100644 index 0000000..6499955 --- /dev/null +++ b/logs/accel_vs_voltage/linear/version_0/hparams.yaml @@ -0,0 +1,3 @@ +input_dim: 2000 +lr: 0.001 +output_dim: 2000 diff --git a/logs/accel_vs_voltage/version_0/hparams.yaml b/logs/accel_vs_voltage/version_0/hparams.yaml new file mode 100644 index 0000000..26df763 --- /dev/null +++ b/logs/accel_vs_voltage/version_0/hparams.yaml @@ -0,0 +1,6 @@ +dropout: 0.2 +hidden_dim: 64 +input_dim: 2000 +lr: 0.001 +num_layers: 2 +output_dim: 2000 diff --git a/logs/accel_vs_voltage/version_0/metrics.csv b/logs/accel_vs_voltage/version_0/metrics.csv new file mode 100644 index 0000000..decf14d --- /dev/null +++ b/logs/accel_vs_voltage/version_0/metrics.csv @@ -0,0 +1,381 @@ +epoch,step,train_loss,val_loss +0,9,232.81629943847656, +0,19,235.33441162109375, +0,29,231.98622131347656, +0,39,221.85794067382812, +0,49,200.1230926513672, +0,59,164.76202392578125, +0,65,,146.8446044921875 +1,69,142.9142303466797, +1,79,150.541748046875, +1,89,142.82028198242188, +1,99,143.50567626953125, +1,109,141.5503387451172, +1,119,141.63558959960938, +1,129,142.07534790039062, +1,131,,140.702392578125 +2,139,142.72406005859375, +2,149,138.79049682617188, +2,159,141.6927490234375, +2,169,140.85653686523438, +2,179,143.96531677246094, +2,189,144.72325134277344, +2,197,,140.4781036376953 +3,199,143.36553955078125, +3,209,139.98690795898438, +3,219,138.6521453857422, +3,229,148.1422119140625, +3,239,145.45846557617188, +3,249,141.4232177734375, +3,259,138.16610717773438, +3,263,,140.41868591308594 +4,269,138.27308654785156, +4,279,138.9016876220703, +4,289,142.4536590576172, +4,299,140.69131469726562, +4,309,141.67616271972656, +4,319,140.73574829101562, +4,329,142.4419403076172, +4,329,,140.3440704345703 +5,339,140.9569549560547, +5,349,140.70965576171875, +5,359,136.0340118408203, +5,369,137.99253845214844, +5,379,139.06361389160156, +5,389,140.15521240234375, +5,395,,140.246826171875 +6,399,142.62738037109375, +6,409,141.14881896972656, +6,419,141.0486602783203, +6,429,140.9000244140625, +6,439,144.39822387695312, +6,449,141.54994201660156, +6,459,141.43312072753906, +6,461,,140.20591735839844 +7,469,139.3676300048828, +7,479,143.01927185058594, +7,489,142.95159912109375, +7,499,144.21397399902344, +7,509,137.67184448242188, +7,519,140.3195343017578, +7,527,,140.16534423828125 +8,529,141.2534637451172, +8,539,146.5666961669922, +8,549,137.3173370361328, +8,559,145.49832153320312, +8,569,139.60658264160156, +8,579,136.3116912841797, +8,589,139.4369659423828, +8,593,,140.21372985839844 +9,599,142.64956665039062, +9,609,142.3053741455078, +9,619,140.63543701171875, +9,629,140.8764190673828, +9,639,146.96434020996094, +9,649,143.82965087890625, +9,659,135.6006317138672, +9,659,,140.1498565673828 +10,669,143.49595642089844, +10,679,140.55572509765625, +10,689,144.48153686523438, +10,699,141.7393798828125, +10,709,140.34678649902344, +10,719,140.30262756347656, +10,725,,140.09332275390625 +11,729,140.06069946289062, +11,739,138.03408813476562, +11,749,135.74331665039062, +11,759,145.12123107910156, +11,769,141.91102600097656, +11,779,134.864501953125, +11,789,141.65965270996094, +11,791,,140.11912536621094 +12,799,144.77310180664062, +12,809,135.57492065429688, +12,819,140.32962036132812, +12,829,136.74696350097656, +12,839,141.40711975097656, +12,849,141.11767578125, +12,857,,140.09429931640625 +13,859,142.05630493164062, +13,869,138.54652404785156, +13,879,139.1221923828125, +13,889,145.18490600585938, +13,899,139.8895263671875, +13,909,146.1988067626953, +13,919,141.1463623046875, +13,923,,139.98573303222656 +14,929,139.6596221923828, +14,939,142.7782440185547, +14,949,135.580810546875, +14,959,139.4943084716797, +14,969,139.02865600585938, +14,979,136.50662231445312, +14,989,137.7846221923828, +14,989,,140.01434326171875 +15,999,139.6349334716797, +15,1009,139.39923095703125, +15,1019,138.1359100341797, +15,1029,139.02745056152344, +15,1039,139.93380737304688, +15,1049,137.24256896972656, +15,1055,,139.94342041015625 +16,1059,141.7335662841797, +16,1069,143.60865783691406, +16,1079,141.33680725097656, +16,1089,137.4942626953125, +16,1099,141.6973419189453, +16,1109,141.27896118164062, +16,1119,143.1510009765625, +16,1121,,139.84442138671875 +17,1129,137.89002990722656, +17,1139,140.8849639892578, +17,1149,137.781982421875, +17,1159,139.7733917236328, +17,1169,141.55947875976562, +17,1179,136.92288208007812, +17,1187,,139.75050354003906 +18,1189,141.6465301513672, +18,1199,144.40220642089844, +18,1209,136.8129425048828, +18,1219,138.74452209472656, +18,1229,140.5390625, +18,1239,139.5146942138672, +18,1249,141.29812622070312, +18,1253,,139.6305694580078 +19,1259,139.4655303955078, +19,1269,139.44482421875, +19,1279,136.20762634277344, +19,1289,138.49398803710938, +19,1299,135.3817138671875, +19,1309,145.2625274658203, +19,1319,141.27601623535156, +19,1319,,139.40785217285156 +20,1329,140.7897491455078, +20,1339,140.75717163085938, +20,1349,138.6880340576172, +20,1359,131.68206787109375, +20,1369,139.60194396972656, +20,1379,143.22503662109375, +20,1385,,138.97364807128906 +21,1389,142.45909118652344, +21,1399,138.02206420898438, +21,1409,141.5945281982422, +21,1419,135.8405303955078, +21,1429,142.16696166992188, +21,1439,135.22250366210938, +21,1449,136.46360778808594, +21,1451,,138.32931518554688 +22,1459,137.26344299316406, +22,1469,137.12953186035156, +22,1479,140.05224609375, +22,1489,136.65269470214844, +22,1499,137.6255340576172, +22,1509,139.16500854492188, +22,1517,,137.4369659423828 +23,1519,137.86160278320312, +23,1529,136.31991577148438, +23,1539,141.4786376953125, +23,1549,136.9014434814453, +23,1559,139.87294006347656, +23,1569,135.88412475585938, +23,1579,139.67434692382812, +23,1583,,136.42169189453125 +24,1589,135.91021728515625, +24,1599,137.00027465820312, +24,1609,135.4778289794922, +24,1619,137.33837890625, +24,1629,133.5034637451172, +24,1639,135.90133666992188, +24,1649,133.02684020996094, +24,1649,,135.4620819091797 +25,1659,136.35665893554688, +25,1669,132.25233459472656, +25,1679,130.01100158691406, +25,1689,134.8028564453125, +25,1699,133.04959106445312, +25,1709,132.27452087402344, +25,1715,,134.8671417236328 +26,1719,130.9766387939453, +26,1729,137.10809326171875, +26,1739,136.869384765625, +26,1749,135.3996124267578, +26,1759,134.86245727539062, +26,1769,131.898681640625, +26,1779,130.75201416015625, +26,1781,,134.49896240234375 +27,1789,129.6650848388672, +27,1799,136.54139709472656, +27,1809,134.61085510253906, +27,1819,133.09547424316406, +27,1829,132.1442413330078, +27,1839,135.3035888671875, +27,1847,,134.18223571777344 +28,1849,133.2232208251953, +28,1859,133.65652465820312, +28,1869,135.1230010986328, +28,1879,134.33975219726562, +28,1889,128.99082946777344, +28,1899,131.21578979492188, +28,1909,132.57693481445312, +28,1913,,133.7671356201172 +29,1919,134.0696258544922, +29,1929,137.9094696044922, +29,1939,130.8016357421875, +29,1949,132.61322021484375, +29,1959,128.8699493408203, +29,1969,134.7858428955078, +29,1979,134.67543029785156, +29,1979,,133.2554473876953 +30,1989,129.94970703125, +30,1999,132.08262634277344, +30,2009,130.3333740234375, +30,2019,132.65443420410156, +30,2029,131.74339294433594, +30,2039,135.28286743164062, +30,2045,,132.67376708984375 +31,2049,130.425537109375, +31,2059,133.56033325195312, +31,2069,127.85145568847656, +31,2079,133.536376953125, +31,2089,131.4635467529297, +31,2099,133.11431884765625, +31,2109,130.19190979003906, +31,2111,,132.19696044921875 +32,2119,133.6085662841797, +32,2129,133.34347534179688, +32,2139,130.8061065673828, +32,2149,127.63047790527344, +32,2159,131.89366149902344, +32,2169,130.7673797607422, +32,2177,,131.69430541992188 +33,2179,134.64158630371094, +33,2189,132.9971923828125, +33,2199,130.741455078125, +33,2209,125.54096984863281, +33,2219,132.98855590820312, +33,2229,129.27786254882812, +33,2239,131.28627014160156, +33,2243,,131.38458251953125 +34,2249,132.31044006347656, +34,2259,133.35934448242188, +34,2269,128.41943359375, +34,2279,125.4333724975586, +34,2289,129.0288848876953, +34,2299,131.4931182861328, +34,2309,128.70516967773438, +34,2309,,131.1314239501953 +35,2319,130.01385498046875, +35,2329,133.32835388183594, +35,2339,131.4668426513672, +35,2349,127.31842041015625, +35,2359,126.70516967773438, +35,2369,126.63903045654297, +35,2375,,130.98255920410156 +36,2379,130.4451141357422, +36,2389,132.33340454101562, +36,2399,130.91453552246094, +36,2409,132.0982666015625, +36,2419,129.8077392578125, +36,2429,128.52255249023438, +36,2439,132.37997436523438, +36,2441,,130.74722290039062 +37,2449,127.38014221191406, +37,2459,129.49151611328125, +37,2469,130.40928649902344, +37,2479,132.20465087890625, +37,2489,129.70761108398438, +37,2499,128.1068878173828, +37,2507,,130.38995361328125 +38,2509,126.13877868652344, +38,2519,126.74577331542969, +38,2529,126.66204833984375, +38,2539,129.07839965820312, +38,2549,128.6352996826172, +38,2559,125.97232055664062, +38,2569,131.18954467773438, +38,2573,,129.9959259033203 +39,2579,124.59490203857422, +39,2589,134.09544372558594, +39,2599,129.70343017578125, +39,2609,127.77167510986328, +39,2619,125.5830307006836, +39,2629,129.77545166015625, +39,2639,127.19851684570312, +39,2639,,129.4800567626953 +40,2649,127.51185607910156, +40,2659,126.38912963867188, +40,2669,123.57137298583984, +40,2679,128.599365234375, +40,2689,134.7974395751953, +40,2699,126.9178466796875, +40,2705,,129.19387817382812 +41,2709,133.62258911132812, +41,2719,124.69148254394531, +41,2729,122.31336212158203, +41,2739,129.92784118652344, +41,2749,125.40959167480469, +41,2759,127.81179809570312, +41,2769,126.0780029296875, +41,2771,,128.93450927734375 +42,2779,127.23684692382812, +42,2789,128.9568634033203, +42,2799,130.08767700195312, +42,2809,124.37364196777344, +42,2819,129.2450714111328, +42,2829,127.36824798583984, +42,2837,,128.8045654296875 +43,2839,125.15625762939453, +43,2849,125.38384246826172, +43,2859,127.92493438720703, +43,2869,127.48817443847656, +43,2879,129.339111328125, +43,2889,125.87267303466797, +43,2899,126.42748260498047, +43,2903,,128.51052856445312 +44,2909,126.87074279785156, +44,2919,126.53483581542969, +44,2929,129.15188598632812, +44,2939,127.75853729248047, +44,2949,128.25372314453125, +44,2959,128.78858947753906, +44,2969,125.72706604003906, +44,2969,,128.29176330566406 +45,2979,125.56273651123047, +45,2989,126.5289077758789, +45,2999,127.30494689941406, +45,3009,127.05184173583984, +45,3019,127.50814056396484, +45,3029,126.67189025878906, +45,3035,,127.99984741210938 +46,3039,123.94223022460938, +46,3049,123.57229614257812, +46,3059,125.6963119506836, +46,3069,128.28834533691406, +46,3079,123.36548614501953, +46,3089,125.92878723144531, +46,3099,126.41043853759766, +46,3101,,127.69361877441406 +47,3109,127.32520294189453, +47,3119,126.36927032470703, +47,3129,128.4836883544922, +47,3139,123.40267181396484, +47,3149,125.05936431884766, +47,3159,121.59667205810547, +47,3167,,127.57974243164062 +48,3169,123.9828872680664, +48,3179,123.80512237548828, +48,3189,125.42438507080078, +48,3199,128.63076782226562, +48,3209,124.71866607666016, +48,3219,127.83795166015625, +48,3229,125.85395050048828, +48,3233,,127.41996002197266 +49,3239,127.24809265136719, +49,3249,122.65509033203125, +49,3259,128.06285095214844, +49,3269,119.55409240722656, +49,3279,123.87429809570312, +49,3289,126.56134033203125, +49,3299,125.85235595703125, +49,3299,,127.32292938232422 diff --git a/logs/accel_vs_voltage/version_1/hparams.yaml b/logs/accel_vs_voltage/version_1/hparams.yaml new file mode 100644 index 0000000..a24825d --- /dev/null +++ b/logs/accel_vs_voltage/version_1/hparams.yaml @@ -0,0 +1,6 @@ +dropout: 0.2 +hidden_dim: 64 +input_dim: 2000 +lr: 0.01 +num_layers: 2 +output_dim: 2000 diff --git a/logs/accel_vs_voltage/version_1/metrics.csv b/logs/accel_vs_voltage/version_1/metrics.csv new file mode 100644 index 0000000..6d3da64 --- /dev/null +++ b/logs/accel_vs_voltage/version_1/metrics.csv @@ -0,0 +1,148 @@ +epoch,step,train_loss,val_loss +0,9,180.673583984375, +0,19,157.0355224609375, +0,29,144.47059631347656, +0,39,143.99916076660156, +0,49,142.18020629882812, +0,59,143.00852966308594, +0,65,,141.2587432861328 +1,69,141.5295867919922, +1,79,148.49549865722656, +1,89,143.0091552734375, +1,99,143.10543823242188, +1,109,141.26138305664062, +1,119,140.96603393554688, +1,129,141.98300170898438, +1,131,,139.7860870361328 +2,139,140.90133666992188, +2,149,138.50384521484375, +2,159,138.87002563476562, +2,169,139.4585418701172, +2,179,141.887939453125, +2,189,141.32984924316406, +2,197,,137.41917419433594 +3,199,139.99237060546875, +3,209,135.8304443359375, +3,219,134.49305725097656, +3,229,142.3383331298828, +3,239,139.41952514648438, +3,249,137.64085388183594, +3,259,132.48916625976562, +3,263,,133.8144073486328 +4,269,130.23353576660156, +4,279,131.22637939453125, +4,289,133.45388793945312, +4,299,134.748291015625, +4,309,133.70550537109375, +4,319,132.12088012695312, +4,329,132.76211547851562, +4,329,,131.94052124023438 +5,339,132.38619995117188, +5,349,133.36904907226562, +5,359,126.17273712158203, +5,369,129.19476318359375, +5,379,130.9667510986328, +5,389,131.88279724121094, +5,395,,130.58642578125 +6,399,129.87307739257812, +6,409,129.6077423095703, +6,419,127.19175720214844, +6,429,127.42224884033203, +6,439,131.15977478027344, +6,449,128.93853759765625, +6,459,131.0526580810547, +6,461,,128.92945861816406 +7,469,126.6879653930664, +7,479,128.2059783935547, +7,489,128.68605041503906, +7,499,131.3035125732422, +7,509,123.54609680175781, +7,519,127.1180419921875, +7,527,,127.20889282226562 +8,529,125.84579467773438, +8,539,128.7979736328125, +8,549,123.94022369384766, +8,559,130.43817138671875, +8,569,125.93473815917969, +8,579,121.27162170410156, +8,589,125.71390533447266, +8,593,,126.38662719726562 +9,599,123.96894073486328, +9,609,124.12725830078125, +9,619,127.59009552001953, +9,629,124.15196228027344, +9,639,130.78529357910156, +9,649,128.60464477539062, +9,659,121.48328399658203, +9,659,,125.96919250488281 +10,669,126.30612182617188, +10,679,122.83932495117188, +10,689,127.56377410888672, +10,699,127.58758544921875, +10,709,123.78765106201172, +10,719,123.66477966308594, +10,725,,124.81622314453125 +11,729,123.68439483642578, +11,739,119.80818939208984, +11,749,117.72740936279297, +11,759,123.7246322631836, +11,769,122.58769989013672, +11,779,115.89156341552734, +11,789,125.7635498046875, +11,791,,123.97713470458984 +12,799,124.11245727539062, +12,809,117.58898162841797, +12,819,120.12740325927734, +12,829,119.2411117553711, +12,839,121.73849487304688, +12,849,123.31919860839844, +12,857,,123.58428192138672 +13,859,121.39275360107422, +13,869,119.83617401123047, +13,879,118.88710021972656, +13,889,123.57598114013672, +13,899,118.41559600830078, +13,909,123.94230651855469, +13,919,123.26957702636719, +13,923,,122.5628662109375 +14,929,119.39337158203125, +14,939,121.3318862915039, +14,949,118.44075012207031, +14,959,119.4417495727539, +14,969,119.87212371826172, +14,979,117.82801055908203, +14,989,120.23500061035156, +14,989,,122.02286529541016 +15,999,118.91146087646484, +15,1009,117.25159454345703, +15,1019,118.6279525756836, +15,1029,118.24604797363281, +15,1039,121.05857849121094, +15,1049,119.21031188964844, +15,1055,,121.6494369506836 +16,1059,118.68138122558594, +16,1069,120.88115692138672, +16,1079,118.40252685546875, +16,1089,117.084228515625, +16,1099,120.01298522949219, +16,1109,121.33184814453125, +16,1119,119.08972930908203, +16,1121,,120.59695434570312 +17,1129,116.2994384765625, +17,1139,118.5998764038086, +17,1149,112.45214080810547, +17,1159,121.58628845214844, +17,1169,121.11357879638672, +17,1179,114.45315551757812, +17,1187,,120.05026245117188 +18,1189,119.69075012207031, +18,1199,116.80426025390625, +18,1209,116.64686584472656, +18,1219,115.59400177001953, +18,1229,117.493896484375, +18,1239,116.3048095703125, +18,1249,118.3450698852539, +18,1253,,119.12319946289062 +19,1259,116.69544982910156, +19,1269,116.40937805175781, +19,1279,114.52159118652344, diff --git a/logs/accel_vs_voltage/version_2/hparams.yaml b/logs/accel_vs_voltage/version_2/hparams.yaml new file mode 100644 index 0000000..517d7b3 --- /dev/null +++ b/logs/accel_vs_voltage/version_2/hparams.yaml @@ -0,0 +1,6 @@ +dropout: 0.2 +hidden_dim: 32 +input_dim: 2000 +lr: 0.01 +num_layers: 1 +output_dim: 2000 diff --git a/logs/accel_vs_voltage/version_2/metrics.csv b/logs/accel_vs_voltage/version_2/metrics.csv new file mode 100644 index 0000000..145fb49 --- /dev/null +++ b/logs/accel_vs_voltage/version_2/metrics.csv @@ -0,0 +1,282 @@ +epoch,step,train_loss,val_loss +0,9,226.3666229248047, +0,19,180.61000061035156, +0,29,162.53465270996094, +0,39,147.75608825683594, +0,49,148.2689666748047, +0,59,142.9584197998047, +0,65,,144.28211975097656 +1,69,147.68087768554688, +1,79,140.81394958496094, +1,89,143.6970977783203, +1,99,144.74302673339844, +1,109,143.6501922607422, +1,119,142.74456787109375, +1,129,140.96066284179688, +1,131,,140.9616241455078 +2,139,137.3954620361328, +2,149,134.53228759765625, +2,159,137.8950653076172, +2,169,141.50869750976562, +2,179,137.7833251953125, +2,189,132.92724609375, +2,197,,136.68093872070312 +3,199,134.28094482421875, +3,209,133.32177734375, +3,219,138.3447723388672, +3,229,136.04144287109375, +3,239,135.09011840820312, +3,249,134.6930694580078, +3,259,135.21011352539062, +3,263,,133.13145446777344 +4,269,131.6627197265625, +4,279,127.20867919921875, +4,289,128.12118530273438, +4,299,129.86685180664062, +4,309,127.8243408203125, +4,319,127.65460205078125, +4,329,132.43190002441406, +4,329,,130.6439971923828 +5,339,128.78408813476562, +5,349,125.87361907958984, +5,359,122.25762176513672, +5,369,124.83395385742188, +5,379,122.53190612792969, +5,389,123.1092758178711, +5,395,,128.40843200683594 +6,399,125.74998474121094, +6,409,127.16947174072266, +6,419,123.78567504882812, +6,429,124.46823120117188, +6,439,123.67706298828125, +6,449,125.74037170410156, +6,459,125.452392578125, +6,461,,126.42920684814453 +7,469,125.4803466796875, +7,479,122.74925994873047, +7,489,122.10330963134766, +7,499,119.57691192626953, +7,509,121.9892349243164, +7,519,121.93524169921875, +7,527,,124.8940200805664 +8,529,120.27658081054688, +8,539,123.13709259033203, +8,549,123.94469451904297, +8,559,123.09077453613281, +8,569,119.7975082397461, +8,579,122.17784118652344, +8,589,125.22003936767578, +8,593,,123.71078491210938 +9,599,121.74818420410156, +9,609,117.50031280517578, +9,619,116.5751724243164, +9,629,118.28861236572266, +9,639,117.66213989257812, +9,649,123.21143341064453, +9,659,120.06696319580078, +9,659,,122.61324310302734 +10,669,119.71179962158203, +10,679,119.72994995117188, +10,689,115.99359893798828, +10,699,113.71354675292969, +10,709,119.84443664550781, +10,719,119.1895751953125, +10,725,,121.91131591796875 +11,729,115.53575134277344, +11,739,117.67110443115234, +11,749,115.68382263183594, +11,759,116.16160583496094, +11,769,119.01766204833984, +11,779,122.9393081665039, +11,789,116.93650817871094, +11,791,,121.3022232055664 +12,799,118.61872100830078, +12,809,116.36595153808594, +12,819,118.14482879638672, +12,829,117.7730941772461, +12,839,120.90833282470703, +12,849,116.82164001464844, +12,857,,120.7388916015625 +13,859,117.50180053710938, +13,869,115.463134765625, +13,879,116.35223388671875, +13,889,117.94679260253906, +13,899,116.01753997802734, +13,909,115.55598449707031, +13,919,115.74420166015625, +13,923,,120.69593811035156 +14,929,115.79606628417969, +14,939,118.94666290283203, +14,949,113.00117492675781, +14,959,117.59183502197266, +14,969,116.15465545654297, +14,979,113.02944946289062, +14,989,115.80987548828125, +14,989,,120.03231811523438 +15,999,117.39204406738281, +15,1009,115.03892517089844, +15,1019,114.71257781982422, +15,1029,117.73589324951172, +15,1039,118.06694793701172, +15,1049,116.56575012207031, +15,1055,,120.31024932861328 +16,1059,111.28295135498047, +16,1069,117.06585693359375, +16,1079,114.93629455566406, +16,1089,113.54176330566406, +16,1099,115.90868377685547, +16,1109,114.23741149902344, +16,1119,115.6397705078125, +16,1121,,119.90868377685547 +17,1129,117.12416076660156, +17,1139,111.59361267089844, +17,1149,113.14442443847656, +17,1159,115.91458129882812, +17,1169,121.0655517578125, +17,1179,116.5186996459961, +17,1187,,119.84950256347656 +18,1189,112.84719848632812, +18,1199,109.61769104003906, +18,1209,112.56965637207031, +18,1219,112.06531524658203, +18,1229,110.8528060913086, +18,1239,115.31394958496094, +18,1249,115.71137237548828, +18,1253,,119.51829528808594 +19,1259,112.4548110961914, +19,1269,114.44415283203125, +19,1279,116.68377685546875, +19,1289,115.28682708740234, +19,1299,114.83126831054688, +19,1309,116.9713134765625, +19,1319,112.42037963867188, +19,1319,,119.66299438476562 +20,1329,115.03150177001953, +20,1339,113.26910400390625, +20,1349,115.65312957763672, +20,1359,116.43720245361328, +20,1369,114.26647186279297, +20,1379,113.45631408691406, +20,1385,,119.00508117675781 +21,1389,112.3015365600586, +21,1399,111.5501480102539, +21,1409,114.99579620361328, +21,1419,107.31014251708984, +21,1429,118.92542266845703, +21,1439,114.67058563232422, +21,1449,115.53168487548828, +21,1451,,118.88634490966797 +22,1459,114.89994049072266, +22,1469,114.32502746582031, +22,1479,113.72820281982422, +22,1489,113.02619934082031, +22,1499,113.10397338867188, +22,1509,114.7218017578125, +22,1517,,119.0986557006836 +23,1519,110.07946014404297, +23,1529,112.38631439208984, +23,1539,112.86764526367188, +23,1549,114.62390899658203, +23,1559,111.92369079589844, +23,1569,121.23239135742188, +23,1579,112.60663604736328, +23,1583,,118.93489837646484 +24,1589,111.6075210571289, +24,1599,114.56604766845703, +24,1609,109.99993133544922, +24,1619,113.582763671875, +24,1629,110.4630126953125, +24,1639,112.23973083496094, +24,1649,114.938232421875, +24,1649,,119.05054473876953 +25,1659,109.41686248779297, +25,1669,117.09773254394531, +25,1679,114.94656372070312, +25,1689,115.42862701416016, +25,1699,114.37506103515625, +25,1709,113.59040832519531, +25,1715,,119.2099838256836 +26,1719,114.69978332519531, +26,1729,114.20957946777344, +26,1739,112.62632751464844, +26,1749,114.28990936279297, +26,1759,113.40548706054688, +26,1769,112.8271255493164, +26,1779,114.02008056640625, +26,1781,,118.61045837402344 +27,1789,111.98313903808594, +27,1799,113.54096984863281, +27,1809,110.75137329101562, +27,1819,115.18315887451172, +27,1829,113.35935974121094, +27,1839,114.87287139892578, +27,1847,,119.00375366210938 +28,1849,111.59227752685547, +28,1859,112.65753173828125, +28,1869,113.92506408691406, +28,1879,110.31745910644531, +28,1889,113.5835952758789, +28,1899,113.75688934326172, +28,1909,113.10997009277344, +28,1913,,118.8275375366211 +29,1919,111.79269409179688, +29,1929,113.79236602783203, +29,1939,109.01176452636719, +29,1949,113.1526107788086, +29,1959,111.00067138671875, +29,1969,116.83251190185547, +29,1979,117.45176696777344, +29,1979,,118.98319244384766 +30,1989,111.12554931640625, +30,1999,116.5609359741211, +30,2009,113.68988800048828, +30,2019,113.30867004394531, +30,2029,112.564453125, +30,2039,112.69818878173828, +30,2045,,118.87507629394531 +31,2049,113.05412292480469, +31,2059,114.70154571533203, +31,2069,116.81698608398438, +31,2079,109.35723114013672, +31,2089,112.82493591308594, +31,2099,112.06246948242188, +31,2109,117.740966796875, +31,2111,,118.64384460449219 +32,2119,114.22357940673828, +32,2129,114.20282745361328, +32,2139,111.67523193359375, +32,2149,112.44924926757812, +32,2159,112.3403091430664, +32,2169,118.5964126586914, +32,2177,,118.9365463256836 +33,2179,113.48704528808594, +33,2189,115.02953338623047, +33,2199,111.13034057617188, +33,2209,113.84965515136719, +33,2219,114.62264251708984, +33,2229,107.3004379272461, +33,2239,113.93258666992188, +33,2243,,118.69916534423828 +34,2249,112.13536834716797, +34,2259,111.66222381591797, +34,2269,116.67870330810547, +34,2279,116.4195556640625, +34,2289,112.43524932861328, +34,2299,109.29354858398438, +34,2309,110.73224639892578, +34,2309,,118.73929595947266 +35,2319,113.4383773803711, +35,2329,109.22058868408203, +35,2339,115.99308013916016, +35,2349,114.96504974365234, +35,2359,117.72956085205078, +35,2369,111.11326599121094, +35,2375,,118.90662384033203 +36,2379,115.32356262207031, +36,2389,114.35138702392578, +36,2399,109.7108154296875, +36,2409,117.07617950439453, +36,2419,113.78744506835938, +36,2429,112.03490447998047, +36,2439,112.31515502929688, +36,2441,,118.82929992675781 diff --git a/main.py b/main.py index 6e721f7..630a841 100644 --- a/main.py +++ b/main.py @@ -1,16 +1,15 @@ from __future__ import annotations import argparse -import json import logging from pathlib import Path from typing import Any, Dict, List -import pandas as pd import yaml -from src import control_validation, data_acquisition, data_augmentation, data_cleaning, data_loader, model_training, report_generation -from src.utils import configure_logging, ensure_dir, resolve_path, set_global_seed +from dronecontrol.data_process.preparation import prepare_scenario_data +from dronecontrol.model_training import train_models_for_scenario +from dronecontrol.utils import configure_logging, ensure_dir, resolve_path, set_global_seed LOGGER = logging.getLogger(__name__) @@ -59,108 +58,20 @@ def run_pipeline(config_path: Path, selected_scenarios: List[str] | None = None) seed = int(scenario.get("training", {}).get("seed", general_cfg.get("seed", 42))) set_global_seed(seed) - processed_dir = Path(general_cfg.get("default_processed_dir", "data/processed")) - data_bundle = data_acquisition.assemble_dataset( - scenario_name, - scenario, - project_root, - processed_dir if processed_dir.is_absolute() else project_root / processed_dir, - ) - - inputs_df = data_bundle["inputs"].copy() - outputs_df = data_bundle["outputs"].copy() - numeric_inputs = inputs_df.select_dtypes(include="number").columns - numeric_outputs = outputs_df.select_dtypes(include="number").columns - - pre_cfg = dict(scenario.get("preprocessing", {})) - combined_df = pd.concat([inputs_df, outputs_df], axis=1) - combined_df = data_cleaning.remove_outliers(combined_df, method=pre_cfg.get("outlier_method", "iqr")) - combined_df = data_cleaning.interpolate_missing(combined_df, method=pre_cfg.get("fill_method", "linear")) - combined_df, norm_params = data_cleaning.normalize( - combined_df, - scope=pre_cfg.get("normalization_scope", "per_feature"), - ) - - inputs_clean = combined_df[numeric_inputs] - outputs_clean = combined_df[numeric_outputs] - - X_array, Y_array = data_loader.dataframe_to_arrays(inputs_clean, outputs_clean) - X_array, Y_array = data_augmentation.append_simulink_samples( - X_array, - Y_array, - data_bundle.get("simulink_data"), - ) - - aug_cfg = dict(scenario.get("augmentation", {})) - noise_std = float(aug_cfg.get("noise_std", 0.0)) - X_aug, Y_aug = data_augmentation.add_gaussian_noise(X_array, Y_array, noise_std=noise_std, seed=seed) - - window_size = int(aug_cfg.get("window_size", 1)) - window_stride = int(aug_cfg.get("window_stride", 1)) - windowed = data_augmentation.slice_windows( - X_aug, - Y_aug, - window_size=window_size, - stride=window_stride, - ) - - train_cfg = dict(scenario.get("training", {})) - split_arrays = data_loader.train_val_test_split( - windowed["X"], - windowed["Y"], - val_ratio=float(train_cfg.get("val_split", 0.2)), - test_ratio=float(train_cfg.get("test_split", 0.1)), - seed=seed, - ) - - processed_path = data_bundle["processed_path"] - data_loader.save_npz(processed_path, split_arrays) + data_module, data_meta = prepare_scenario_data(scenario, project_root) - checkpoints = model_training.train_models_for_scenario( + checkpoints = train_models_for_scenario( scenario_name, scenario, general_cfg, - processed_path, + data_module, + data_meta["input_dim"], + data_meta["output_dim"], ) - metrics_path = control_validation.validate_control( - scenario_name, - scenario, - general_cfg, - processed_path, - checkpoints, - ) - - report_generation.generate_report( - scenario_name, - scenario, - general_cfg, - processed_path, - metrics_path, - checkpoints, - ) - - _update_metadata(processed_path, { - "seed": seed, - "normalization": norm_params, - "window_size": window_size, - "window_stride": window_stride, - "noise_std": noise_std, - }) - LOGGER.info("=== Completed scenario: %s ===", scenario_name) -def _update_metadata(processed_path: str, updates: Dict[str, Any]) -> None: - meta_path = Path(processed_path).with_suffix(".meta.json") - if meta_path.exists(): - meta = json.loads(meta_path.read_text()) - else: - meta = {} - meta.update(updates) - meta_path.write_text(json.dumps(meta, indent=2)) - - def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description="Run UAV data-driven control pipeline") parser.add_argument("--config", type=Path, default=Path("config.yaml"), help="Path to configuration file") diff --git a/matlabfiles/codegen/dll/quadcopter_model/_clang-format b/matlabfiles/codegen/dll/quadcopter_model/_clang-format new file mode 100644 index 0000000..55a16f7 --- /dev/null +++ b/matlabfiles/codegen/dll/quadcopter_model/_clang-format @@ -0,0 +1,27 @@ +BasedOnStyle: LLVM +Language: Cpp +IndentWidth: 2 +ColumnLimit: 80 +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +CommentPragmas: '^(Return Type|Arguments)\s*' +DeriveLineEnding : true +BreakBeforeBraces: Custom +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: true + AfterFunction: true + AfterNamespace: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + SplitEmptyFunction: true + SplitEmptyRecord: true diff --git a/matlabfiles/codegen/dll/quadcopter_model/buildInfo.mat b/matlabfiles/codegen/dll/quadcopter_model/buildInfo.mat new file mode 100644 index 0000000..92b6733 Binary files /dev/null and b/matlabfiles/codegen/dll/quadcopter_model/buildInfo.mat differ diff --git a/matlabfiles/codegen/dll/quadcopter_model/codeInfo.mat b/matlabfiles/codegen/dll/quadcopter_model/codeInfo.mat new file mode 100644 index 0000000..1c28395 Binary files /dev/null and b/matlabfiles/codegen/dll/quadcopter_model/codeInfo.mat differ diff --git a/matlabfiles/codegen/dll/quadcopter_model/codedescriptor.dmr b/matlabfiles/codegen/dll/quadcopter_model/codedescriptor.dmr new file mode 100644 index 0000000..66a2c37 Binary files /dev/null and b/matlabfiles/codegen/dll/quadcopter_model/codedescriptor.dmr differ diff --git a/matlabfiles/codegen/dll/quadcopter_model/compileInfo.mat b/matlabfiles/codegen/dll/quadcopter_model/compileInfo.mat new file mode 100644 index 0000000..5d84189 Binary files /dev/null and b/matlabfiles/codegen/dll/quadcopter_model/compileInfo.mat differ diff --git a/matlabfiles/codegen/dll/quadcopter_model/examples/main.c b/matlabfiles/codegen/dll/quadcopter_model/examples/main.c new file mode 100644 index 0000000..afdeef0 --- /dev/null +++ b/matlabfiles/codegen/dll/quadcopter_model/examples/main.c @@ -0,0 +1,106 @@ +/* + * Academic License - for use in teaching, academic research, and meeting + * course requirements at degree granting institutions only. Not for + * government, commercial, or other organizational use. + * + * main.c + * + * Code generation for function 'main' + * + */ + +/*************************************************************************/ +/* This automatically generated example C main file shows how to call */ +/* entry-point functions that MATLAB Coder generated. You must customize */ +/* this file for your application. Do not modify this file directly. */ +/* Instead, make a copy of this file, modify it, and integrate it into */ +/* your development environment. */ +/* */ +/* This file initializes entry-point function arguments to a default */ +/* size and value before calling the entry-point functions. It does */ +/* not store or use any values returned from the entry-point functions. */ +/* If necessary, it does pre-allocate memory for returned values. */ +/* You can use this file as a starting point for a main function that */ +/* you can deploy in your application. */ +/* */ +/* After you copy the file, and before you deploy it, you must make the */ +/* following changes: */ +/* * For variable-size function arguments, change the example sizes to */ +/* the sizes that your application requires. */ +/* * Change the example values of function arguments to the values that */ +/* your application requires. */ +/* * If the entry-point functions return values, store these values or */ +/* otherwise use them as required by your application. */ +/* */ +/*************************************************************************/ + +/* Include files */ +#include "main.h" +#include "quadcopter_model.h" +#include "quadcopter_model_terminate.h" + +/* Function Declarations */ +static void argInit_12x1_real_T(double result[12]); + +static void argInit_4x1_real_T(double result[4]); + +static double argInit_real_T(void); + +/* Function Definitions */ +static void argInit_12x1_real_T(double result[12]) +{ + int idx0; + /* Loop over the array to initialize each element. */ + for (idx0 = 0; idx0 < 12; idx0++) { + /* Set the value of the array element. +Change this value to the value that the application requires. */ + result[idx0] = argInit_real_T(); + } +} + +static void argInit_4x1_real_T(double result[4]) +{ + int idx0; + /* Loop over the array to initialize each element. */ + for (idx0 = 0; idx0 < 4; idx0++) { + /* Set the value of the array element. +Change this value to the value that the application requires. */ + result[idx0] = argInit_real_T(); + } +} + +static double argInit_real_T(void) +{ + return 0.0; +} + +int main(int argc, char **argv) +{ + (void)argc; + (void)argv; + /* The initialize function is being called automatically from your entry-point + * function. So, a call to initialize is not included here. */ + /* Invoke the entry-point functions. +You can call entry-point functions multiple times. */ + main_quadcopter_model(); + /* Terminate the application. +You do not need to do this more than one time. */ + quadcopter_model_terminate(); + return 0; +} + +void main_quadcopter_model(void) +{ + double dv[12]; + double dxdt[12]; + double dv1[4]; + /* Initialize function 'quadcopter_model' input arguments. */ + /* Initialize function input argument 'x'. */ + /* Initialize function input argument 'u'. */ + /* Call the entry-point 'quadcopter_model'. */ + argInit_12x1_real_T(dv); + argInit_4x1_real_T(dv1); + quadcopter_model(dv, dv1, dxdt); +} + +/* End of code generation (main.c) */ diff --git a/matlabfiles/codegen/dll/quadcopter_model/examples/main.h b/matlabfiles/codegen/dll/quadcopter_model/examples/main.h new file mode 100644 index 0000000..fb41aea --- /dev/null +++ b/matlabfiles/codegen/dll/quadcopter_model/examples/main.h @@ -0,0 +1,59 @@ +/* + * Academic License - for use in teaching, academic research, and meeting + * course requirements at degree granting institutions only. Not for + * government, commercial, or other organizational use. + * + * main.h + * + * Code generation for function 'main' + * + */ + +/*************************************************************************/ +/* This automatically generated example C main file shows how to call */ +/* entry-point functions that MATLAB Coder generated. You must customize */ +/* this file for your application. Do not modify this file directly. */ +/* Instead, make a copy of this file, modify it, and integrate it into */ +/* your development environment. */ +/* */ +/* This file initializes entry-point function arguments to a default */ +/* size and value before calling the entry-point functions. It does */ +/* not store or use any values returned from the entry-point functions. */ +/* If necessary, it does pre-allocate memory for returned values. */ +/* You can use this file as a starting point for a main function that */ +/* you can deploy in your application. */ +/* */ +/* After you copy the file, and before you deploy it, you must make the */ +/* following changes: */ +/* * For variable-size function arguments, change the example sizes to */ +/* the sizes that your application requires. */ +/* * Change the example values of function arguments to the values that */ +/* your application requires. */ +/* * If the entry-point functions return values, store these values or */ +/* otherwise use them as required by your application. */ +/* */ +/*************************************************************************/ + +#ifndef MAIN_H +#define MAIN_H + +/* Include files */ +#include "rtwtypes.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Function Declarations */ +extern int main(int argc, char **argv); + +extern void main_quadcopter_model(void); + +#ifdef __cplusplus +} +#endif + +#endif +/* End of code generation (main.h) */ diff --git a/matlabfiles/codegen/dll/quadcopter_model/interface/_coder_quadcopter_model_api.c b/matlabfiles/codegen/dll/quadcopter_model/interface/_coder_quadcopter_model_api.c new file mode 100644 index 0000000..bfb56c4 --- /dev/null +++ b/matlabfiles/codegen/dll/quadcopter_model/interface/_coder_quadcopter_model_api.c @@ -0,0 +1,204 @@ +/* + * Academic License - for use in teaching, academic research, and meeting + * course requirements at degree granting institutions only. Not for + * government, commercial, or other organizational use. + * + * _coder_quadcopter_model_api.c + * + * Code generation for function 'quadcopter_model' + * + */ + +/* Include files */ +#include "_coder_quadcopter_model_api.h" +#include "_coder_quadcopter_model_mex.h" + +/* Variable Definitions */ +emlrtCTX emlrtRootTLSGlobal = NULL; + +emlrtContext emlrtContextGlobal = { + true, /* bFirstTime */ + false, /* bInitialized */ + 131643U, /* fVersionInfo */ + NULL, /* fErrorFunction */ + "quadcopter_model", /* fFunctionName */ + NULL, /* fRTCallStack */ + false, /* bDebugMode */ + {2045744189U, 2170104910U, 2743257031U, 4284093946U}, /* fSigWrd */ + NULL /* fSigMem */ +}; + +/* Function Declarations */ +static real_T (*b_emlrt_marshallIn(const emlrtStack *sp, const mxArray *u, + const emlrtMsgIdentifier *parentId))[12]; + +static real_T (*c_emlrt_marshallIn(const emlrtStack *sp, const mxArray *nullptr, + const char_T *identifier))[4]; + +static real_T (*d_emlrt_marshallIn(const emlrtStack *sp, const mxArray *u, + const emlrtMsgIdentifier *parentId))[4]; + +static real_T (*e_emlrt_marshallIn(const emlrtStack *sp, const mxArray *src, + const emlrtMsgIdentifier *msgId))[12]; + +static void emlrtExitTimeCleanupDtorFcn(const void *r); + +static real_T (*emlrt_marshallIn(const emlrtStack *sp, const mxArray *nullptr, + const char_T *identifier))[12]; + +static const mxArray *emlrt_marshallOut(const real_T u[12]); + +static real_T (*f_emlrt_marshallIn(const emlrtStack *sp, const mxArray *src, + const emlrtMsgIdentifier *msgId))[4]; + +/* Function Definitions */ +static real_T (*b_emlrt_marshallIn(const emlrtStack *sp, const mxArray *u, + const emlrtMsgIdentifier *parentId))[12] +{ + real_T(*y)[12]; + y = e_emlrt_marshallIn(sp, emlrtAlias(u), parentId); + emlrtDestroyArray(&u); + return y; +} + +static real_T (*c_emlrt_marshallIn(const emlrtStack *sp, const mxArray *nullptr, + const char_T *identifier))[4] +{ + emlrtMsgIdentifier thisId; + real_T(*y)[4]; + thisId.fIdentifier = (const char_T *)identifier; + thisId.fParent = NULL; + thisId.bParentIsCell = false; + y = d_emlrt_marshallIn(sp, emlrtAlias(nullptr), &thisId); + emlrtDestroyArray(&nullptr); + return y; +} + +static real_T (*d_emlrt_marshallIn(const emlrtStack *sp, const mxArray *u, + const emlrtMsgIdentifier *parentId))[4] +{ + real_T(*y)[4]; + y = f_emlrt_marshallIn(sp, emlrtAlias(u), parentId); + emlrtDestroyArray(&u); + return y; +} + +static real_T (*e_emlrt_marshallIn(const emlrtStack *sp, const mxArray *src, + const emlrtMsgIdentifier *msgId))[12] +{ + static const int32_T dims = 12; + real_T(*ret)[12]; + int32_T i; + boolean_T b = false; + emlrtCheckVsBuiltInR2012b((emlrtConstCTX)sp, msgId, src, "double", false, 1U, + (const void *)&dims, &b, &i); + ret = (real_T(*)[12])emlrtMxGetData(src); + emlrtDestroyArray(&src); + return ret; +} + +static void emlrtExitTimeCleanupDtorFcn(const void *r) +{ + emlrtExitTimeCleanup(&emlrtContextGlobal); +} + +static real_T (*emlrt_marshallIn(const emlrtStack *sp, const mxArray *nullptr, + const char_T *identifier))[12] +{ + emlrtMsgIdentifier thisId; + real_T(*y)[12]; + thisId.fIdentifier = (const char_T *)identifier; + thisId.fParent = NULL; + thisId.bParentIsCell = false; + y = b_emlrt_marshallIn(sp, emlrtAlias(nullptr), &thisId); + emlrtDestroyArray(&nullptr); + return y; +} + +static const mxArray *emlrt_marshallOut(const real_T u[12]) +{ + static const int32_T i = 0; + static const int32_T i1 = 12; + const mxArray *m; + const mxArray *y; + y = NULL; + m = emlrtCreateNumericArray(1, (const void *)&i, mxDOUBLE_CLASS, mxREAL); + emlrtMxSetData((mxArray *)m, (void *)&u[0]); + emlrtSetDimensions((mxArray *)m, &i1, 1); + emlrtAssign(&y, m); + return y; +} + +static real_T (*f_emlrt_marshallIn(const emlrtStack *sp, const mxArray *src, + const emlrtMsgIdentifier *msgId))[4] +{ + static const int32_T dims = 4; + real_T(*ret)[4]; + int32_T i; + boolean_T b = false; + emlrtCheckVsBuiltInR2012b((emlrtConstCTX)sp, msgId, src, "double", false, 1U, + (const void *)&dims, &b, &i); + ret = (real_T(*)[4])emlrtMxGetData(src); + emlrtDestroyArray(&src); + return ret; +} + +void quadcopter_model_api(const mxArray *const prhs[2], const mxArray **plhs) +{ + emlrtStack st = { + NULL, /* site */ + NULL, /* tls */ + NULL /* prev */ + }; + real_T(*dxdt)[12]; + real_T(*x)[12]; + real_T(*u)[4]; + st.tls = emlrtRootTLSGlobal; + dxdt = (real_T(*)[12])mxMalloc(sizeof(real_T[12])); + /* Marshall function inputs */ + x = emlrt_marshallIn(&st, emlrtAlias(prhs[0]), "x"); + u = c_emlrt_marshallIn(&st, emlrtAlias(prhs[1]), "u"); + /* Invoke the target function */ + quadcopter_model(*x, *u, *dxdt); + /* Marshall function outputs */ + *plhs = emlrt_marshallOut(*dxdt); +} + +void quadcopter_model_atexit(void) +{ + emlrtStack st = { + NULL, /* site */ + NULL, /* tls */ + NULL /* prev */ + }; + mexFunctionCreateRootTLS(); + st.tls = emlrtRootTLSGlobal; + emlrtPushHeapReferenceStackR2021a( + &st, false, NULL, (void *)&emlrtExitTimeCleanupDtorFcn, NULL, NULL, NULL); + emlrtEnterRtStackR2012b(&st); + emlrtDestroyRootTLS(&emlrtRootTLSGlobal); + quadcopter_model_xil_terminate(); + quadcopter_model_xil_shutdown(); + emlrtExitTimeCleanup(&emlrtContextGlobal); +} + +void quadcopter_model_initialize(void) +{ + emlrtStack st = { + NULL, /* site */ + NULL, /* tls */ + NULL /* prev */ + }; + mexFunctionCreateRootTLS(); + st.tls = emlrtRootTLSGlobal; + emlrtClearAllocCountR2012b(&st, false, 0U, NULL); + emlrtEnterRtStackR2012b(&st); + emlrtFirstTimeR2012b(emlrtRootTLSGlobal); +} + +void quadcopter_model_terminate(void) +{ + emlrtDestroyRootTLS(&emlrtRootTLSGlobal); +} + +/* End of code generation (_coder_quadcopter_model_api.c) */ diff --git a/matlabfiles/codegen/dll/quadcopter_model/interface/_coder_quadcopter_model_api.h b/matlabfiles/codegen/dll/quadcopter_model/interface/_coder_quadcopter_model_api.h new file mode 100644 index 0000000..949b994 --- /dev/null +++ b/matlabfiles/codegen/dll/quadcopter_model/interface/_coder_quadcopter_model_api.h @@ -0,0 +1,49 @@ +/* + * Academic License - for use in teaching, academic research, and meeting + * course requirements at degree granting institutions only. Not for + * government, commercial, or other organizational use. + * + * _coder_quadcopter_model_api.h + * + * Code generation for function 'quadcopter_model' + * + */ + +#ifndef _CODER_QUADCOPTER_MODEL_API_H +#define _CODER_QUADCOPTER_MODEL_API_H + +/* Include files */ +#include "emlrt.h" +#include "mex.h" +#include "tmwtypes.h" +#include + +/* Variable Declarations */ +extern emlrtCTX emlrtRootTLSGlobal; +extern emlrtContext emlrtContextGlobal; + +#ifdef __cplusplus +extern "C" { +#endif + +/* Function Declarations */ +void quadcopter_model(real_T x[12], real_T u[4], real_T dxdt[12]); + +void quadcopter_model_api(const mxArray *const prhs[2], const mxArray **plhs); + +void quadcopter_model_atexit(void); + +void quadcopter_model_initialize(void); + +void quadcopter_model_terminate(void); + +void quadcopter_model_xil_shutdown(void); + +void quadcopter_model_xil_terminate(void); + +#ifdef __cplusplus +} +#endif + +#endif +/* End of code generation (_coder_quadcopter_model_api.h) */ diff --git a/matlabfiles/codegen/dll/quadcopter_model/interface/_coder_quadcopter_model_info.c b/matlabfiles/codegen/dll/quadcopter_model/interface/_coder_quadcopter_model_info.c new file mode 100644 index 0000000..c0c9902 --- /dev/null +++ b/matlabfiles/codegen/dll/quadcopter_model/interface/_coder_quadcopter_model_info.c @@ -0,0 +1,80 @@ +/* + * Academic License - for use in teaching, academic research, and meeting + * course requirements at degree granting institutions only. Not for + * government, commercial, or other organizational use. + * + * _coder_quadcopter_model_info.c + * + * Code generation for function 'quadcopter_model' + * + */ + +/* Include files */ +#include "_coder_quadcopter_model_info.h" +#include "emlrt.h" +#include "tmwtypes.h" + +/* Function Declarations */ +static const mxArray *c_emlrtMexFcnResolvedFunctionsI(void); + +/* Function Definitions */ +static const mxArray *c_emlrtMexFcnResolvedFunctionsI(void) +{ + static const int32_T iv[2] = {0, 1}; + const mxArray *m; + const mxArray *nameCaptureInfo; + nameCaptureInfo = NULL; + m = emlrtCreateNumericArray(2, (const void *)&iv[0], mxDOUBLE_CLASS, mxREAL); + emlrtAssign(&nameCaptureInfo, m); + return nameCaptureInfo; +} + +mxArray *emlrtMexFcnProperties(void) +{ + mxArray *xEntryPoints; + mxArray *xInputs; + mxArray *xResult; + const char_T *propFieldName[9] = {"Version", + "ResolvedFunctions", + "Checksum", + "EntryPoints", + "CoverageInfo", + "IsPolymorphic", + "PropertyList", + "UUID", + "ClassEntryPointIsHandle"}; + const char_T *epFieldName[8] = { + "Name", "NumberOfInputs", "NumberOfOutputs", "ConstantInputs", + "FullPath", "TimeStamp", "Constructor", "Visible"}; + xEntryPoints = + emlrtCreateStructMatrix(1, 1, 8, (const char_T **)&epFieldName[0]); + xInputs = emlrtCreateLogicalMatrix(1, 2); + emlrtSetField(xEntryPoints, 0, "Name", + emlrtMxCreateString("quadcopter_model")); + emlrtSetField(xEntryPoints, 0, "NumberOfInputs", + emlrtMxCreateDoubleScalar(2.0)); + emlrtSetField(xEntryPoints, 0, "NumberOfOutputs", + emlrtMxCreateDoubleScalar(1.0)); + emlrtSetField(xEntryPoints, 0, "ConstantInputs", xInputs); + emlrtSetField(xEntryPoints, 0, "FullPath", + emlrtMxCreateString( + "C:\\Users\\baudo\\Desktop\\ST " + "DDC\\dronecontrol\\matlabfiles\\quadcopter_model.m")); + emlrtSetField(xEntryPoints, 0, "TimeStamp", + emlrtMxCreateDoubleScalar(739926.46809027775)); + emlrtSetField(xEntryPoints, 0, "Constructor", + emlrtMxCreateLogicalScalar(false)); + emlrtSetField(xEntryPoints, 0, "Visible", emlrtMxCreateLogicalScalar(true)); + xResult = + emlrtCreateStructMatrix(1, 1, 9, (const char_T **)&propFieldName[0]); + emlrtSetField(xResult, 0, "Version", + emlrtMxCreateString("24.1.0.2837808 (R2024a) Update 7")); + emlrtSetField(xResult, 0, "ResolvedFunctions", + (mxArray *)c_emlrtMexFcnResolvedFunctionsI()); + emlrtSetField(xResult, 0, "Checksum", + emlrtMxCreateString("UZNV6GSDjNHqUjgyPa3PcG")); + emlrtSetField(xResult, 0, "EntryPoints", xEntryPoints); + return xResult; +} + +/* End of code generation (_coder_quadcopter_model_info.c) */ diff --git a/matlabfiles/codegen/dll/quadcopter_model/interface/_coder_quadcopter_model_info.h b/matlabfiles/codegen/dll/quadcopter_model/interface/_coder_quadcopter_model_info.h new file mode 100644 index 0000000..d18a8e0 --- /dev/null +++ b/matlabfiles/codegen/dll/quadcopter_model/interface/_coder_quadcopter_model_info.h @@ -0,0 +1,30 @@ +/* + * Academic License - for use in teaching, academic research, and meeting + * course requirements at degree granting institutions only. Not for + * government, commercial, or other organizational use. + * + * _coder_quadcopter_model_info.h + * + * Code generation for function 'quadcopter_model' + * + */ + +#ifndef _CODER_QUADCOPTER_MODEL_INFO_H +#define _CODER_QUADCOPTER_MODEL_INFO_H + +/* Include files */ +#include "mex.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Function Declarations */ +MEXFUNCTION_LINKAGE mxArray *emlrtMexFcnProperties(void); + +#ifdef __cplusplus +} +#endif + +#endif +/* End of code generation (_coder_quadcopter_model_info.h) */ diff --git a/matlabfiles/codegen/dll/quadcopter_model/interface/_coder_quadcopter_model_mex.c b/matlabfiles/codegen/dll/quadcopter_model/interface/_coder_quadcopter_model_mex.c new file mode 100644 index 0000000..fe018d7 --- /dev/null +++ b/matlabfiles/codegen/dll/quadcopter_model/interface/_coder_quadcopter_model_mex.c @@ -0,0 +1,64 @@ +/* + * Academic License - for use in teaching, academic research, and meeting + * course requirements at degree granting institutions only. Not for + * government, commercial, or other organizational use. + * + * _coder_quadcopter_model_mex.c + * + * Code generation for function 'quadcopter_model' + * + */ + +/* Include files */ +#include "_coder_quadcopter_model_mex.h" +#include "_coder_quadcopter_model_api.h" + +/* Function Definitions */ +void mexFunction(int32_T nlhs, mxArray *plhs[], int32_T nrhs, + const mxArray *prhs[]) +{ + mexAtExit(&quadcopter_model_atexit); + /* Module initialization. */ + quadcopter_model_initialize(); + /* Dispatch the entry-point. */ + unsafe_quadcopter_model_mexFunction(nlhs, plhs, nrhs, prhs); + /* Module termination. */ + quadcopter_model_terminate(); +} + +emlrtCTX mexFunctionCreateRootTLS(void) +{ + emlrtCreateRootTLSR2022a(&emlrtRootTLSGlobal, &emlrtContextGlobal, NULL, 1, + NULL, "windows-1252", true); + return emlrtRootTLSGlobal; +} + +void unsafe_quadcopter_model_mexFunction(int32_T nlhs, mxArray *plhs[1], + int32_T nrhs, const mxArray *prhs[2]) +{ + emlrtStack st = { + NULL, /* site */ + NULL, /* tls */ + NULL /* prev */ + }; + const mxArray *b_prhs[2]; + const mxArray *outputs; + st.tls = emlrtRootTLSGlobal; + /* Check for proper number of arguments. */ + if (nrhs != 2) { + emlrtErrMsgIdAndTxt(&st, "EMLRT:runTime:WrongNumberOfInputs", 5, 12, 2, 4, + 16, "quadcopter_model"); + } + if (nlhs > 1) { + emlrtErrMsgIdAndTxt(&st, "EMLRT:runTime:TooManyOutputArguments", 3, 4, 16, + "quadcopter_model"); + } + /* Call the function. */ + b_prhs[0] = prhs[0]; + b_prhs[1] = prhs[1]; + quadcopter_model_api(b_prhs, &outputs); + /* Copy over outputs to the caller. */ + emlrtReturnArrays(1, &plhs[0], &outputs); +} + +/* End of code generation (_coder_quadcopter_model_mex.c) */ diff --git a/matlabfiles/codegen/dll/quadcopter_model/interface/_coder_quadcopter_model_mex.h b/matlabfiles/codegen/dll/quadcopter_model/interface/_coder_quadcopter_model_mex.h new file mode 100644 index 0000000..cb6ef7e --- /dev/null +++ b/matlabfiles/codegen/dll/quadcopter_model/interface/_coder_quadcopter_model_mex.h @@ -0,0 +1,38 @@ +/* + * Academic License - for use in teaching, academic research, and meeting + * course requirements at degree granting institutions only. Not for + * government, commercial, or other organizational use. + * + * _coder_quadcopter_model_mex.h + * + * Code generation for function 'quadcopter_model' + * + */ + +#ifndef _CODER_QUADCOPTER_MODEL_MEX_H +#define _CODER_QUADCOPTER_MODEL_MEX_H + +/* Include files */ +#include "emlrt.h" +#include "mex.h" +#include "tmwtypes.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Function Declarations */ +MEXFUNCTION_LINKAGE void mexFunction(int32_T nlhs, mxArray *plhs[], + int32_T nrhs, const mxArray *prhs[]); + +emlrtCTX mexFunctionCreateRootTLS(void); + +void unsafe_quadcopter_model_mexFunction(int32_T nlhs, mxArray *plhs[1], + int32_T nrhs, const mxArray *prhs[2]); + +#ifdef __cplusplus +} +#endif + +#endif +/* End of code generation (_coder_quadcopter_model_mex.h) */ diff --git a/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model.c b/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model.c new file mode 100644 index 0000000..9f5027d --- /dev/null +++ b/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model.c @@ -0,0 +1,173 @@ +/* + * Academic License - for use in teaching, academic research, and meeting + * course requirements at degree granting institutions only. Not for + * government, commercial, or other organizational use. + * + * quadcopter_model.c + * + * Code generation for function 'quadcopter_model' + * + */ + +/* Include files */ +#include "quadcopter_model.h" +#include +#include + +/* Function Definitions */ +void quadcopter_model(const double x[12], const double u[4], double dxdt[12]) +{ + static const double b_a[9] = {0.1, 0.0, 0.0, 0.0, 0.1, 0.0, 0.0, 0.0, 0.2}; + static const double dv[3] = {0.0, 0.0, 9.81}; + static const signed char c_a[9] = {10, 0, 0, 0, 10, 0, 0, 0, 5}; + double c_R_y_tmp[9]; + double c_R_z_tmp[9]; + double d_R_z_tmp[9]; + double accel[3]; + double angular_acc[3]; + double b_u[3]; + double y[3]; + double R_x_tmp; + double R_y_tmp; + double R_z_tmp; + double a; + double b_R_x_tmp; + double b_R_y_tmp; + double b_R_z_tmp; + double d; + int R_z_tmp_tmp; + int i; + int i1; + /* Parameters of the quadcopter */ + /* Mass of the quadcopter (kg) */ + /* Gravity (m/s^2) */ + /* Inertia matrix (kg*m^2) */ + /* Distance from center of mass to motors (m) */ + /* Thrust constant (N per unit input) */ + /* Moment constant (N*m per unit input) */ + /* Quadcopter state vector */ + /* Position (x, y, z) */ + /* Velocity (vx, vy, vz) */ + /* Euler angles (phi, theta, psi) */ + /* Angular velocity (wx, wy, wz) */ + /* Rotation matrix (from global frame to body frame) */ + /* Function to convert Euler angles to rotation matrix */ + /* Roll */ + /* Pitch */ + /* Yaw */ + /* Rotation matrix about x-axis (roll) */ + R_x_tmp = sin(x[6]); + b_R_x_tmp = cos(x[6]); + /* Rotation matrix about y-axis (pitch) */ + R_y_tmp = sin(x[7]); + b_R_y_tmp = cos(x[7]); + /* Rotation matrix about z-axis (yaw) */ + R_z_tmp = sin(x[8]); + b_R_z_tmp = cos(x[8]); + /* Combined rotation matrix: R = Rz * Ry * Rx */ + /* Motor forces (thrust and torques) */ + /* Thrust from motor 1 */ + /* Thrust from motor 2 */ + /* Thrust from motor 3 */ + /* Thrust from motor 4 */ + /* Compute total thrust from all motors */ + a = ((u[0] + u[1]) + u[2]) + u[3]; + /* Total thrust force */ + /* Compute torques generated by the motors */ + /* Roll torque */ + /* Pitch torque */ + /* Yaw torque */ + /* Total thrust force vector in world frame (aligned with body z-axis) */ + /* Thrust direction (z-axis of body) */ + /* Translational dynamics (acceleration) */ + c_R_z_tmp[0] = b_R_z_tmp; + c_R_z_tmp[3] = -R_z_tmp; + c_R_z_tmp[6] = 0.0; + c_R_z_tmp[1] = R_z_tmp; + c_R_z_tmp[4] = b_R_z_tmp; + c_R_z_tmp[7] = 0.0; + c_R_y_tmp[0] = b_R_y_tmp; + c_R_y_tmp[3] = 0.0; + c_R_y_tmp[6] = R_y_tmp; + c_R_z_tmp[2] = 0.0; + c_R_y_tmp[1] = 0.0; + c_R_z_tmp[5] = 0.0; + c_R_y_tmp[4] = 1.0; + c_R_z_tmp[8] = 1.0; + c_R_y_tmp[7] = 0.0; + c_R_y_tmp[2] = -R_y_tmp; + c_R_y_tmp[5] = 0.0; + c_R_y_tmp[8] = b_R_y_tmp; + for (i = 0; i < 3; i++) { + b_R_z_tmp = c_R_z_tmp[i]; + d = c_R_z_tmp[i + 3]; + i1 = (int)c_R_z_tmp[i + 6]; + for (R_z_tmp_tmp = 0; R_z_tmp_tmp < 3; R_z_tmp_tmp++) { + d_R_z_tmp[i + 3 * R_z_tmp_tmp] = + (b_R_z_tmp * c_R_y_tmp[3 * R_z_tmp_tmp] + + d * c_R_y_tmp[3 * R_z_tmp_tmp + 1]) + + (double)i1 * c_R_y_tmp[3 * R_z_tmp_tmp + 2]; + } + } + c_R_y_tmp[0] = 1.0; + c_R_y_tmp[3] = 0.0; + c_R_y_tmp[6] = 0.0; + c_R_y_tmp[1] = 0.0; + c_R_y_tmp[4] = b_R_x_tmp; + c_R_y_tmp[7] = -R_x_tmp; + c_R_y_tmp[2] = 0.0; + c_R_y_tmp[5] = R_x_tmp; + c_R_y_tmp[8] = b_R_x_tmp; + /* Acceleration = thrust/mass - gravity */ + /* Rotational dynamics (angular acceleration) */ + b_u[0] = u[1] - u[3]; + b_u[1] = u[2] - u[0]; + b_u[2] = ((u[0] - u[1]) + u[2]) - u[3]; + for (i = 0; i < 3; i++) { + b_R_z_tmp = d_R_z_tmp[i]; + d = d_R_z_tmp[i + 3]; + R_y_tmp = d_R_z_tmp[i + 6]; + b_R_y_tmp = 0.0; + R_z_tmp = 0.0; + for (i1 = 0; i1 < 3; i1++) { + R_z_tmp_tmp = i + 3 * i1; + c_R_z_tmp[R_z_tmp_tmp] = + (b_R_z_tmp * c_R_y_tmp[3 * i1] + d * c_R_y_tmp[3 * i1 + 1]) + + R_y_tmp * c_R_y_tmp[3 * i1 + 2]; + b_R_y_tmp += b_a[R_z_tmp_tmp] * x[i1 + 9]; + R_z_tmp += (double)c_a[R_z_tmp_tmp] * b_u[i1]; + } + angular_acc[i] = R_z_tmp; + y[i] = b_R_y_tmp; + accel[i] = a * c_R_z_tmp[i + 6] / 1.5 - dv[i]; + } + __m128d r; + __m128d r1; + b_u[0] = y[2] * x[10] - y[1] * x[11]; + b_u[1] = y[0] * x[11] - y[2] * x[9]; + b_u[2] = y[1] * x[9] - y[0] * x[10]; + r = _mm_loadu_pd(&angular_acc[0]); + r1 = _mm_loadu_pd(&b_u[0]); + _mm_storeu_pd(&angular_acc[0], _mm_sub_pd(r, r1)); + angular_acc[2] -= b_u[2]; + /* Angular acceleration = torque/inertia - gyroscopic effects */ + /* Compute state derivatives */ + /* Derivative of position is velocity */ + /* Derivative of velocity is acceleration */ + /* Derivative of Euler angles is angular velocity */ + dxdt[0] = x[3]; + dxdt[3] = accel[0]; + dxdt[6] = x[9]; + dxdt[9] = angular_acc[0]; + dxdt[1] = x[4]; + dxdt[4] = accel[1]; + dxdt[7] = x[10]; + dxdt[10] = angular_acc[1]; + dxdt[2] = x[5]; + dxdt[5] = accel[2]; + dxdt[8] = x[11]; + dxdt[11] = angular_acc[2]; + /* Derivative of angular velocity is angular acceleration */ +} + +/* End of code generation (quadcopter_model.c) */ diff --git a/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model.def b/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model.def new file mode 100644 index 0000000..9b6c217 --- /dev/null +++ b/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model.def @@ -0,0 +1,4 @@ +EXPORTS +quadcopter_model +quadcopter_model_initialize +quadcopter_model_terminate diff --git a/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model.dll b/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model.dll new file mode 100644 index 0000000..25d2014 Binary files /dev/null and b/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model.dll differ diff --git a/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model.exp b/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model.exp new file mode 100644 index 0000000..4a363fc Binary files /dev/null and b/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model.exp differ diff --git a/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model.h b/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model.h new file mode 100644 index 0000000..6319bad --- /dev/null +++ b/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model.h @@ -0,0 +1,33 @@ +/* + * Academic License - for use in teaching, academic research, and meeting + * course requirements at degree granting institutions only. Not for + * government, commercial, or other organizational use. + * + * quadcopter_model.h + * + * Code generation for function 'quadcopter_model' + * + */ + +#ifndef QUADCOPTER_MODEL_H +#define QUADCOPTER_MODEL_H + +/* Include files */ +#include "rtwtypes.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Function Declarations */ +extern void quadcopter_model(const double x[12], const double u[4], + double dxdt[12]); + +#ifdef __cplusplus +} +#endif + +#endif +/* End of code generation (quadcopter_model.h) */ diff --git a/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model.lib b/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model.lib new file mode 100644 index 0000000..7651f0e Binary files /dev/null and b/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model.lib differ diff --git a/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model.obj b/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model.obj new file mode 100644 index 0000000..85bc030 Binary files /dev/null and b/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model.obj differ diff --git a/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model_data.h b/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model_data.h new file mode 100644 index 0000000..bfcd75c --- /dev/null +++ b/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model_data.h @@ -0,0 +1,21 @@ +/* + * Academic License - for use in teaching, academic research, and meeting + * course requirements at degree granting institutions only. Not for + * government, commercial, or other organizational use. + * + * quadcopter_model_data.h + * + * Code generation for function 'quadcopter_model_data' + * + */ + +#ifndef QUADCOPTER_MODEL_DATA_H +#define QUADCOPTER_MODEL_DATA_H + +/* Include files */ +#include "rtwtypes.h" +#include +#include + +#endif +/* End of code generation (quadcopter_model_data.h) */ diff --git a/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model_initialize.c b/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model_initialize.c new file mode 100644 index 0000000..c2d418f --- /dev/null +++ b/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model_initialize.c @@ -0,0 +1,20 @@ +/* + * Academic License - for use in teaching, academic research, and meeting + * course requirements at degree granting institutions only. Not for + * government, commercial, or other organizational use. + * + * quadcopter_model_initialize.c + * + * Code generation for function 'quadcopter_model_initialize' + * + */ + +/* Include files */ +#include "quadcopter_model_initialize.h" + +/* Function Definitions */ +void quadcopter_model_initialize(void) +{ +} + +/* End of code generation (quadcopter_model_initialize.c) */ diff --git a/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model_initialize.h b/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model_initialize.h new file mode 100644 index 0000000..4fb5430 --- /dev/null +++ b/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model_initialize.h @@ -0,0 +1,32 @@ +/* + * Academic License - for use in teaching, academic research, and meeting + * course requirements at degree granting institutions only. Not for + * government, commercial, or other organizational use. + * + * quadcopter_model_initialize.h + * + * Code generation for function 'quadcopter_model_initialize' + * + */ + +#ifndef QUADCOPTER_MODEL_INITIALIZE_H +#define QUADCOPTER_MODEL_INITIALIZE_H + +/* Include files */ +#include "rtwtypes.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Function Declarations */ +extern void quadcopter_model_initialize(void); + +#ifdef __cplusplus +} +#endif + +#endif +/* End of code generation (quadcopter_model_initialize.h) */ diff --git a/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model_initialize.obj b/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model_initialize.obj new file mode 100644 index 0000000..bc0d992 Binary files /dev/null and b/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model_initialize.obj differ diff --git a/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model_rtw.bat b/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model_rtw.bat new file mode 100644 index 0000000..ae0cc0c --- /dev/null +++ b/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model_rtw.bat @@ -0,0 +1,17 @@ +@echo off + +set skipSetupArg=%2 +if "%skipSetupArg%" NEQ "skip_setup_msvc" ( +call "setup_msvc.bat" +) + +cd . + +if "%1"=="" (nmake -f quadcopter_model_rtw.mk all) else (nmake -f quadcopter_model_rtw.mk %1) +@if errorlevel 1 goto error_exit + +exit /B 0 + +:error_exit +echo The make command returned an error of %errorlevel% +exit /B 1 \ No newline at end of file diff --git a/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model_rtw.mk b/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model_rtw.mk new file mode 100644 index 0000000..6565709 --- /dev/null +++ b/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model_rtw.mk @@ -0,0 +1,416 @@ +########################################################################### +## Makefile generated for component 'quadcopter_model'. +## +## Makefile : quadcopter_model_rtw.mk +## Generated on : Wed Nov 05 13:18:01 2025 +## Final product: .\quadcopter_model.dll +## Product type : dynamic-library +## +########################################################################### + +########################################################################### +## MACROS +########################################################################### + +# Macro Descriptions: +# PRODUCT_NAME Name of the system to build +# MAKEFILE Name of this makefile +# COMPILER_COMMAND_FILE Compiler command listing model reference header paths +# CMD_FILE Command file +# DEF_FILE Definition file + +PRODUCT_NAME = quadcopter_model +MAKEFILE = quadcopter_model_rtw.mk +MATLAB_ROOT = C:\PROGRA~1\MATLAB\R2024a +MATLAB_BIN = C:\PROGRA~1\MATLAB\R2024a\bin +MATLAB_ARCH_BIN = $(MATLAB_BIN)\win64 +START_DIR = C:\Users\baudo\Desktop\STDDC~1\DRONEC~1\MATLAB~1 +TGT_FCN_LIB = ISO_C +SOLVER_OBJ = +CLASSIC_INTERFACE = 0 +MODEL_HAS_DYNAMICALLY_LOADED_SFCNS = +RELATIVE_PATH_TO_ANCHOR = ..\..\.. +COMPILER_COMMAND_FILE = quadcopter_model_rtw_comp.rsp +CMD_FILE = quadcopter_model_rtw.rsp +DEF_FILE = $(PRODUCT_NAME).def +C_STANDARD_OPTS = +CPP_STANDARD_OPTS = +NODEBUG = 1 + +########################################################################### +## TOOLCHAIN SPECIFICATIONS +########################################################################### + +# Toolchain Name: Microsoft Visual C++ 2022 v17.0 | nmake (64-bit Windows) +# Supported Version(s): 17.0 +# ToolchainInfo Version: 2024a +# Specification Revision: 1.0 +# +#------------------------------------------- +# Macros assumed to be defined elsewhere +#------------------------------------------- + +# NODEBUG +# cvarsdll +# cvarsmt +# conlibsmt +# ldebug +# conflags +# cflags + +#----------- +# MACROS +#----------- + +MW_EXTERNLIB_DIR = $(MATLAB_ROOT)\extern\lib\win64\microsoft +MW_LIB_DIR = $(MATLAB_ROOT)\lib\win64 +CPU = AMD64 +APPVER = 5.02 +CVARSFLAG = $(cvarsmt) +CFLAGS_ADDITIONAL = -D_CRT_SECURE_NO_WARNINGS +CPPFLAGS_ADDITIONAL = -EHs -D_CRT_SECURE_NO_WARNINGS /wd4251 /Zc:__cplusplus +LIBS_TOOLCHAIN = $(conlibs) + +TOOLCHAIN_SRCS = +TOOLCHAIN_INCS = +TOOLCHAIN_LIBS = + +#------------------------ +# BUILD TOOL COMMANDS +#------------------------ + +# C Compiler: Microsoft Visual C Compiler +CC = cl + +# Linker: Microsoft Visual C Linker +LD = link + +# C++ Compiler: Microsoft Visual C++ Compiler +CPP = cl + +# C++ Linker: Microsoft Visual C++ Linker +CPP_LD = link + +# Archiver: Microsoft Visual C/C++ Archiver +AR = lib + +# MEX Tool: MEX Tool +MEX_PATH = $(MATLAB_ARCH_BIN) +MEX = "$(MEX_PATH)\mex" + +# Download: Download +DOWNLOAD = + +# Execute: Execute +EXECUTE = $(PRODUCT) + +# Builder: NMAKE Utility +MAKE = nmake + + +#------------------------- +# Directives/Utilities +#------------------------- + +CDEBUG = -Zi +C_OUTPUT_FLAG = -Fo +LDDEBUG = /DEBUG +OUTPUT_FLAG = -out: +CPPDEBUG = -Zi +CPP_OUTPUT_FLAG = -Fo +CPPLDDEBUG = /DEBUG +OUTPUT_FLAG = -out: +ARDEBUG = +STATICLIB_OUTPUT_FLAG = -out: +MEX_DEBUG = -g +RM = @del +ECHO = @echo +MV = @ren +RUN = @cmd /C + +#-------------------------------------- +# "Faster Runs" Build Configuration +#-------------------------------------- + +ARFLAGS = /nologo +CFLAGS = $(cflags) $(CVARSFLAG) $(CFLAGS_ADDITIONAL) \ + /O2 /Oy- +CPPFLAGS = /TP $(cflags) $(CVARSFLAG) $(CPPFLAGS_ADDITIONAL) \ + /O2 /Oy- +CPP_LDFLAGS = $(ldebug) $(conflags) $(LIBS_TOOLCHAIN) +CPP_SHAREDLIB_LDFLAGS = $(ldebug) $(conflags) $(LIBS_TOOLCHAIN) \ + -dll -def:$(DEF_FILE) +DOWNLOAD_FLAGS = +EXECUTE_FLAGS = +LDFLAGS = $(ldebug) $(conflags) $(LIBS_TOOLCHAIN) +MEX_CPPFLAGS = +MEX_CPPLDFLAGS = +MEX_CFLAGS = +MEX_LDFLAGS = +MAKE_FLAGS = -f $(MAKEFILE) +SHAREDLIB_LDFLAGS = $(ldebug) $(conflags) $(LIBS_TOOLCHAIN) \ + -dll -def:$(DEF_FILE) + + + +########################################################################### +## OUTPUT INFO +########################################################################### + +PRODUCT = .\quadcopter_model.dll +PRODUCT_TYPE = "dynamic-library" +BUILD_TYPE = "Dynamic Library" + +########################################################################### +## INCLUDE PATHS +########################################################################### + +INCLUDES_BUILDINFO = + +INCLUDES = $(INCLUDES_BUILDINFO) + +########################################################################### +## DEFINES +########################################################################### + +DEFINES_CUSTOM = +DEFINES_STANDARD = -DMODEL=quadcopter_model + +DEFINES = $(DEFINES_CUSTOM) $(DEFINES_STANDARD) + +########################################################################### +## SOURCE FILES +########################################################################### + +SRCS = $(START_DIR)\codegen\dll\quadcopter_model\quadcopter_model_initialize.c $(START_DIR)\codegen\dll\quadcopter_model\quadcopter_model_terminate.c $(START_DIR)\codegen\dll\quadcopter_model\quadcopter_model.c + +ALL_SRCS = $(SRCS) + +########################################################################### +## OBJECTS +########################################################################### + +OBJS = quadcopter_model_initialize.obj quadcopter_model_terminate.obj quadcopter_model.obj + +ALL_OBJS = $(OBJS) + +########################################################################### +## PREBUILT OBJECT FILES +########################################################################### + +PREBUILT_OBJS = + +########################################################################### +## LIBRARIES +########################################################################### + +LIBS = + +########################################################################### +## SYSTEM LIBRARIES +########################################################################### + +SYSTEM_LIBS = + +########################################################################### +## ADDITIONAL TOOLCHAIN FLAGS +########################################################################### + +#--------------- +# C Compiler +#--------------- + +CFLAGS_ = /source-charset:utf-8 +CFLAGS_BASIC = $(DEFINES) @$(COMPILER_COMMAND_FILE) + +CFLAGS = $(CFLAGS) $(CFLAGS_) $(CFLAGS_BASIC) + +#----------------- +# C++ Compiler +#----------------- + +CPPFLAGS_ = /source-charset:utf-8 +CPPFLAGS_BASIC = $(DEFINES) @$(COMPILER_COMMAND_FILE) + +CPPFLAGS = $(CPPFLAGS) $(CPPFLAGS_) $(CPPFLAGS_BASIC) + +########################################################################### +## INLINED COMMANDS +########################################################################### + + +!include $(MATLAB_ROOT)\rtw\c\tools\vcdefs.mak + + +########################################################################### +## PHONY TARGETS +########################################################################### + +.PHONY : all build clean info prebuild download execute set_environment_variables + + +all : build + @cmd /C "@echo ### Successfully generated all binary outputs." + + +build : set_environment_variables prebuild $(PRODUCT) + + +prebuild : + + +download : $(PRODUCT) + + +execute : download + + +set_environment_variables : + @set INCLUDE=$(INCLUDES);$(INCLUDE) + @set LIB=$(LIB) + + +########################################################################### +## FINAL TARGET +########################################################################### + +#---------------------------------------- +# Create a dynamic library +#---------------------------------------- + +$(PRODUCT) : $(OBJS) $(PREBUILT_OBJS) + @cmd /C "@echo ### Creating dynamic library "$(PRODUCT)" ..." + $(LD) $(SHAREDLIB_LDFLAGS) -out:$(PRODUCT) @$(CMD_FILE) $(SYSTEM_LIBS) $(TOOLCHAIN_LIBS) + @cmd /C "@echo ### Created: $(PRODUCT)" + + +########################################################################### +## INTERMEDIATE TARGETS +########################################################################### + +#--------------------- +# SOURCE-TO-OBJECT +#--------------------- + +.c.obj : + $(CC) $(CFLAGS) -Fo"$@" "$<" + + +.cpp.obj : + $(CPP) $(CPPFLAGS) -Fo"$@" "$<" + + +.cc.obj : + $(CPP) $(CPPFLAGS) -Fo"$@" "$<" + + +.cxx.obj : + $(CPP) $(CPPFLAGS) -Fo"$@" "$<" + + +{$(RELATIVE_PATH_TO_ANCHOR)}.c.obj : + $(CC) $(CFLAGS) -Fo"$@" "$<" + + +{$(RELATIVE_PATH_TO_ANCHOR)}.cpp.obj : + $(CPP) $(CPPFLAGS) -Fo"$@" "$<" + + +{$(RELATIVE_PATH_TO_ANCHOR)}.cc.obj : + $(CPP) $(CPPFLAGS) -Fo"$@" "$<" + + +{$(RELATIVE_PATH_TO_ANCHOR)}.cxx.obj : + $(CPP) $(CPPFLAGS) -Fo"$@" "$<" + + +{$(START_DIR)\codegen\dll\quadcopter_model}.c.obj : + $(CC) $(CFLAGS) -Fo"$@" "$<" + + +{$(START_DIR)\codegen\dll\quadcopter_model}.cpp.obj : + $(CPP) $(CPPFLAGS) -Fo"$@" "$<" + + +{$(START_DIR)\codegen\dll\quadcopter_model}.cc.obj : + $(CPP) $(CPPFLAGS) -Fo"$@" "$<" + + +{$(START_DIR)\codegen\dll\quadcopter_model}.cxx.obj : + $(CPP) $(CPPFLAGS) -Fo"$@" "$<" + + +{$(START_DIR)}.c.obj : + $(CC) $(CFLAGS) -Fo"$@" "$<" + + +{$(START_DIR)}.cpp.obj : + $(CPP) $(CPPFLAGS) -Fo"$@" "$<" + + +{$(START_DIR)}.cc.obj : + $(CPP) $(CPPFLAGS) -Fo"$@" "$<" + + +{$(START_DIR)}.cxx.obj : + $(CPP) $(CPPFLAGS) -Fo"$@" "$<" + + +quadcopter_model_initialize.obj : "$(START_DIR)\codegen\dll\quadcopter_model\quadcopter_model_initialize.c" + $(CC) $(CFLAGS) -Fo"$@" "$(START_DIR)\codegen\dll\quadcopter_model\quadcopter_model_initialize.c" + + +quadcopter_model_terminate.obj : "$(START_DIR)\codegen\dll\quadcopter_model\quadcopter_model_terminate.c" + $(CC) $(CFLAGS) -Fo"$@" "$(START_DIR)\codegen\dll\quadcopter_model\quadcopter_model_terminate.c" + + +quadcopter_model.obj : "$(START_DIR)\codegen\dll\quadcopter_model\quadcopter_model.c" + $(CC) $(CFLAGS) -Fo"$@" "$(START_DIR)\codegen\dll\quadcopter_model\quadcopter_model.c" + + +########################################################################### +## DEPENDENCIES +########################################################################### + +$(ALL_OBJS) : rtw_proj.tmw $(COMPILER_COMMAND_FILE) $(MAKEFILE) + + +########################################################################### +## MISCELLANEOUS TARGETS +########################################################################### + +info : + @cmd /C "@echo ### PRODUCT = $(PRODUCT)" + @cmd /C "@echo ### PRODUCT_TYPE = $(PRODUCT_TYPE)" + @cmd /C "@echo ### BUILD_TYPE = $(BUILD_TYPE)" + @cmd /C "@echo ### INCLUDES = $(INCLUDES)" + @cmd /C "@echo ### DEFINES = $(DEFINES)" + @cmd /C "@echo ### ALL_SRCS = $(ALL_SRCS)" + @cmd /C "@echo ### ALL_OBJS = $(ALL_OBJS)" + @cmd /C "@echo ### LIBS = $(LIBS)" + @cmd /C "@echo ### MODELREF_LIBS = $(MODELREF_LIBS)" + @cmd /C "@echo ### SYSTEM_LIBS = $(SYSTEM_LIBS)" + @cmd /C "@echo ### TOOLCHAIN_LIBS = $(TOOLCHAIN_LIBS)" + @cmd /C "@echo ### CFLAGS = $(CFLAGS)" + @cmd /C "@echo ### LDFLAGS = $(LDFLAGS)" + @cmd /C "@echo ### SHAREDLIB_LDFLAGS = $(SHAREDLIB_LDFLAGS)" + @cmd /C "@echo ### CPPFLAGS = $(CPPFLAGS)" + @cmd /C "@echo ### CPP_LDFLAGS = $(CPP_LDFLAGS)" + @cmd /C "@echo ### CPP_SHAREDLIB_LDFLAGS = $(CPP_SHAREDLIB_LDFLAGS)" + @cmd /C "@echo ### ARFLAGS = $(ARFLAGS)" + @cmd /C "@echo ### MEX_CFLAGS = $(MEX_CFLAGS)" + @cmd /C "@echo ### MEX_CPPFLAGS = $(MEX_CPPFLAGS)" + @cmd /C "@echo ### MEX_LDFLAGS = $(MEX_LDFLAGS)" + @cmd /C "@echo ### MEX_CPPLDFLAGS = $(MEX_CPPLDFLAGS)" + @cmd /C "@echo ### DOWNLOAD_FLAGS = $(DOWNLOAD_FLAGS)" + @cmd /C "@echo ### EXECUTE_FLAGS = $(EXECUTE_FLAGS)" + @cmd /C "@echo ### MAKE_FLAGS = $(MAKE_FLAGS)" + + +clean : + $(ECHO) "### Deleting all derived files ..." + @if exist $(PRODUCT) $(RM) $(PRODUCT) + $(RM) $(ALL_OBJS) + $(ECHO) "### Deleted all derived files." + + diff --git a/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model_rtw.rsp b/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model_rtw.rsp new file mode 100644 index 0000000..87c4954 --- /dev/null +++ b/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model_rtw.rsp @@ -0,0 +1,3 @@ +quadcopter_model_initialize.obj +quadcopter_model_terminate.obj +quadcopter_model.obj diff --git a/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model_rtw_comp.rsp b/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model_rtw_comp.rsp new file mode 100644 index 0000000..4493bc2 --- /dev/null +++ b/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model_rtw_comp.rsp @@ -0,0 +1,3 @@ +-I"C:\Users\baudo\Desktop\ST DDC\dronecontrol\matlabfiles\codegen\dll\quadcopter_model" +-I"C:\Users\baudo\Desktop\ST DDC\dronecontrol\matlabfiles" +-I"C:\Program Files\MATLAB\R2024a\extern\include" diff --git a/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model_rtw_ref.rsp b/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model_rtw_ref.rsp new file mode 100644 index 0000000..5f28270 --- /dev/null +++ b/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model_rtw_ref.rsp @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model_terminate.c b/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model_terminate.c new file mode 100644 index 0000000..593617b --- /dev/null +++ b/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model_terminate.c @@ -0,0 +1,20 @@ +/* + * Academic License - for use in teaching, academic research, and meeting + * course requirements at degree granting institutions only. Not for + * government, commercial, or other organizational use. + * + * quadcopter_model_terminate.c + * + * Code generation for function 'quadcopter_model_terminate' + * + */ + +/* Include files */ +#include "quadcopter_model_terminate.h" + +/* Function Definitions */ +void quadcopter_model_terminate(void) +{ +} + +/* End of code generation (quadcopter_model_terminate.c) */ diff --git a/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model_terminate.h b/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model_terminate.h new file mode 100644 index 0000000..e790131 --- /dev/null +++ b/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model_terminate.h @@ -0,0 +1,32 @@ +/* + * Academic License - for use in teaching, academic research, and meeting + * course requirements at degree granting institutions only. Not for + * government, commercial, or other organizational use. + * + * quadcopter_model_terminate.h + * + * Code generation for function 'quadcopter_model_terminate' + * + */ + +#ifndef QUADCOPTER_MODEL_TERMINATE_H +#define QUADCOPTER_MODEL_TERMINATE_H + +/* Include files */ +#include "rtwtypes.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Function Declarations */ +extern void quadcopter_model_terminate(void); + +#ifdef __cplusplus +} +#endif + +#endif +/* End of code generation (quadcopter_model_terminate.h) */ diff --git a/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model_terminate.obj b/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model_terminate.obj new file mode 100644 index 0000000..3811342 Binary files /dev/null and b/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model_terminate.obj differ diff --git a/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model_types.h b/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model_types.h new file mode 100644 index 0000000..ebca2b7 --- /dev/null +++ b/matlabfiles/codegen/dll/quadcopter_model/quadcopter_model_types.h @@ -0,0 +1,19 @@ +/* + * Academic License - for use in teaching, academic research, and meeting + * course requirements at degree granting institutions only. Not for + * government, commercial, or other organizational use. + * + * quadcopter_model_types.h + * + * Code generation for function 'quadcopter_model' + * + */ + +#ifndef QUADCOPTER_MODEL_TYPES_H +#define QUADCOPTER_MODEL_TYPES_H + +/* Include files */ +#include "rtwtypes.h" + +#endif +/* End of code generation (quadcopter_model_types.h) */ diff --git a/matlabfiles/codegen/dll/quadcopter_model/rtw_proj.tmw b/matlabfiles/codegen/dll/quadcopter_model/rtw_proj.tmw new file mode 100644 index 0000000..835ba62 --- /dev/null +++ b/matlabfiles/codegen/dll/quadcopter_model/rtw_proj.tmw @@ -0,0 +1 @@ +Code generation project for quadcopter_model using toolchain "Microsoft Visual C++ 2022 v17.0 | nmake (64-bit Windows)". MATLAB root = C:\Program Files\MATLAB\R2024a. diff --git a/matlabfiles/codegen/dll/quadcopter_model/rtwtypes.h b/matlabfiles/codegen/dll/quadcopter_model/rtwtypes.h new file mode 100644 index 0000000..5e1dde1 --- /dev/null +++ b/matlabfiles/codegen/dll/quadcopter_model/rtwtypes.h @@ -0,0 +1,45 @@ +/* + * Academic License - for use in teaching, academic research, and meeting + * course requirements at degree granting institutions only. Not for + * government, commercial, or other organizational use. + * + * rtwtypes.h + * + * Code generation for function 'quadcopter_model' + * + */ + +#ifndef RTWTYPES_H +#define RTWTYPES_H + +/*=======================================================================* + * Fixed width word size data types: * + * int64_T - signed 64 bit integers * + * uint64_T - unsigned 64 bit integers * + *=======================================================================*/ + +#if defined(__APPLE__) +#ifndef INT64_T +#define INT64_T long +#define FMT64 "l" +#if defined(__LP64__) && !defined(INT_TYPE_64_IS_LONG) +#define INT_TYPE_64_IS_LONG +#endif +#endif +#endif + +#if defined(__APPLE__) +#ifndef UINT64_T +#define UINT64_T unsigned long +#define FMT64 "l" +#if defined(__LP64__) && !defined(INT_TYPE_64_IS_LONG) +#define INT_TYPE_64_IS_LONG +#endif +#endif +#endif + +/* Include files */ +#include "tmwtypes.h" + +#endif +/* End of code generation (rtwtypes.h) */ diff --git a/matlabfiles/codegen/dll/quadcopter_model/setup_msvc.bat b/matlabfiles/codegen/dll/quadcopter_model/setup_msvc.bat new file mode 100644 index 0000000..69633d5 --- /dev/null +++ b/matlabfiles/codegen/dll/quadcopter_model/setup_msvc.bat @@ -0,0 +1,2 @@ +set "VSCMD_START_DIR=%CD%" +CALL "C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Auxiliary\Build\VCVARSALL.BAT " amd64 diff --git a/matlabfiles/codegen/lib/quadcopter_model/_clang-format b/matlabfiles/codegen/lib/quadcopter_model/_clang-format new file mode 100644 index 0000000..55a16f7 --- /dev/null +++ b/matlabfiles/codegen/lib/quadcopter_model/_clang-format @@ -0,0 +1,27 @@ +BasedOnStyle: LLVM +Language: Cpp +IndentWidth: 2 +ColumnLimit: 80 +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +CommentPragmas: '^(Return Type|Arguments)\s*' +DeriveLineEnding : true +BreakBeforeBraces: Custom +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: true + AfterFunction: true + AfterNamespace: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + SplitEmptyFunction: true + SplitEmptyRecord: true diff --git a/matlabfiles/codegen/lib/quadcopter_model/buildInfo.mat b/matlabfiles/codegen/lib/quadcopter_model/buildInfo.mat new file mode 100644 index 0000000..b1406d2 Binary files /dev/null and b/matlabfiles/codegen/lib/quadcopter_model/buildInfo.mat differ diff --git a/matlabfiles/codegen/lib/quadcopter_model/codeInfo.mat b/matlabfiles/codegen/lib/quadcopter_model/codeInfo.mat new file mode 100644 index 0000000..099d8d5 Binary files /dev/null and b/matlabfiles/codegen/lib/quadcopter_model/codeInfo.mat differ diff --git a/matlabfiles/codegen/lib/quadcopter_model/codedescriptor.dmr b/matlabfiles/codegen/lib/quadcopter_model/codedescriptor.dmr new file mode 100644 index 0000000..fd41f09 Binary files /dev/null and b/matlabfiles/codegen/lib/quadcopter_model/codedescriptor.dmr differ diff --git a/matlabfiles/codegen/lib/quadcopter_model/compileInfo.mat b/matlabfiles/codegen/lib/quadcopter_model/compileInfo.mat new file mode 100644 index 0000000..852555a Binary files /dev/null and b/matlabfiles/codegen/lib/quadcopter_model/compileInfo.mat differ diff --git a/matlabfiles/codegen/lib/quadcopter_model/html/report.mldatx b/matlabfiles/codegen/lib/quadcopter_model/html/report.mldatx new file mode 100644 index 0000000..b22f799 Binary files /dev/null and b/matlabfiles/codegen/lib/quadcopter_model/html/report.mldatx differ diff --git a/matlabfiles/codegen/lib/quadcopter_model/interface/_coder_quadcopter_model_api.c b/matlabfiles/codegen/lib/quadcopter_model/interface/_coder_quadcopter_model_api.c new file mode 100644 index 0000000..bfb56c4 --- /dev/null +++ b/matlabfiles/codegen/lib/quadcopter_model/interface/_coder_quadcopter_model_api.c @@ -0,0 +1,204 @@ +/* + * Academic License - for use in teaching, academic research, and meeting + * course requirements at degree granting institutions only. Not for + * government, commercial, or other organizational use. + * + * _coder_quadcopter_model_api.c + * + * Code generation for function 'quadcopter_model' + * + */ + +/* Include files */ +#include "_coder_quadcopter_model_api.h" +#include "_coder_quadcopter_model_mex.h" + +/* Variable Definitions */ +emlrtCTX emlrtRootTLSGlobal = NULL; + +emlrtContext emlrtContextGlobal = { + true, /* bFirstTime */ + false, /* bInitialized */ + 131643U, /* fVersionInfo */ + NULL, /* fErrorFunction */ + "quadcopter_model", /* fFunctionName */ + NULL, /* fRTCallStack */ + false, /* bDebugMode */ + {2045744189U, 2170104910U, 2743257031U, 4284093946U}, /* fSigWrd */ + NULL /* fSigMem */ +}; + +/* Function Declarations */ +static real_T (*b_emlrt_marshallIn(const emlrtStack *sp, const mxArray *u, + const emlrtMsgIdentifier *parentId))[12]; + +static real_T (*c_emlrt_marshallIn(const emlrtStack *sp, const mxArray *nullptr, + const char_T *identifier))[4]; + +static real_T (*d_emlrt_marshallIn(const emlrtStack *sp, const mxArray *u, + const emlrtMsgIdentifier *parentId))[4]; + +static real_T (*e_emlrt_marshallIn(const emlrtStack *sp, const mxArray *src, + const emlrtMsgIdentifier *msgId))[12]; + +static void emlrtExitTimeCleanupDtorFcn(const void *r); + +static real_T (*emlrt_marshallIn(const emlrtStack *sp, const mxArray *nullptr, + const char_T *identifier))[12]; + +static const mxArray *emlrt_marshallOut(const real_T u[12]); + +static real_T (*f_emlrt_marshallIn(const emlrtStack *sp, const mxArray *src, + const emlrtMsgIdentifier *msgId))[4]; + +/* Function Definitions */ +static real_T (*b_emlrt_marshallIn(const emlrtStack *sp, const mxArray *u, + const emlrtMsgIdentifier *parentId))[12] +{ + real_T(*y)[12]; + y = e_emlrt_marshallIn(sp, emlrtAlias(u), parentId); + emlrtDestroyArray(&u); + return y; +} + +static real_T (*c_emlrt_marshallIn(const emlrtStack *sp, const mxArray *nullptr, + const char_T *identifier))[4] +{ + emlrtMsgIdentifier thisId; + real_T(*y)[4]; + thisId.fIdentifier = (const char_T *)identifier; + thisId.fParent = NULL; + thisId.bParentIsCell = false; + y = d_emlrt_marshallIn(sp, emlrtAlias(nullptr), &thisId); + emlrtDestroyArray(&nullptr); + return y; +} + +static real_T (*d_emlrt_marshallIn(const emlrtStack *sp, const mxArray *u, + const emlrtMsgIdentifier *parentId))[4] +{ + real_T(*y)[4]; + y = f_emlrt_marshallIn(sp, emlrtAlias(u), parentId); + emlrtDestroyArray(&u); + return y; +} + +static real_T (*e_emlrt_marshallIn(const emlrtStack *sp, const mxArray *src, + const emlrtMsgIdentifier *msgId))[12] +{ + static const int32_T dims = 12; + real_T(*ret)[12]; + int32_T i; + boolean_T b = false; + emlrtCheckVsBuiltInR2012b((emlrtConstCTX)sp, msgId, src, "double", false, 1U, + (const void *)&dims, &b, &i); + ret = (real_T(*)[12])emlrtMxGetData(src); + emlrtDestroyArray(&src); + return ret; +} + +static void emlrtExitTimeCleanupDtorFcn(const void *r) +{ + emlrtExitTimeCleanup(&emlrtContextGlobal); +} + +static real_T (*emlrt_marshallIn(const emlrtStack *sp, const mxArray *nullptr, + const char_T *identifier))[12] +{ + emlrtMsgIdentifier thisId; + real_T(*y)[12]; + thisId.fIdentifier = (const char_T *)identifier; + thisId.fParent = NULL; + thisId.bParentIsCell = false; + y = b_emlrt_marshallIn(sp, emlrtAlias(nullptr), &thisId); + emlrtDestroyArray(&nullptr); + return y; +} + +static const mxArray *emlrt_marshallOut(const real_T u[12]) +{ + static const int32_T i = 0; + static const int32_T i1 = 12; + const mxArray *m; + const mxArray *y; + y = NULL; + m = emlrtCreateNumericArray(1, (const void *)&i, mxDOUBLE_CLASS, mxREAL); + emlrtMxSetData((mxArray *)m, (void *)&u[0]); + emlrtSetDimensions((mxArray *)m, &i1, 1); + emlrtAssign(&y, m); + return y; +} + +static real_T (*f_emlrt_marshallIn(const emlrtStack *sp, const mxArray *src, + const emlrtMsgIdentifier *msgId))[4] +{ + static const int32_T dims = 4; + real_T(*ret)[4]; + int32_T i; + boolean_T b = false; + emlrtCheckVsBuiltInR2012b((emlrtConstCTX)sp, msgId, src, "double", false, 1U, + (const void *)&dims, &b, &i); + ret = (real_T(*)[4])emlrtMxGetData(src); + emlrtDestroyArray(&src); + return ret; +} + +void quadcopter_model_api(const mxArray *const prhs[2], const mxArray **plhs) +{ + emlrtStack st = { + NULL, /* site */ + NULL, /* tls */ + NULL /* prev */ + }; + real_T(*dxdt)[12]; + real_T(*x)[12]; + real_T(*u)[4]; + st.tls = emlrtRootTLSGlobal; + dxdt = (real_T(*)[12])mxMalloc(sizeof(real_T[12])); + /* Marshall function inputs */ + x = emlrt_marshallIn(&st, emlrtAlias(prhs[0]), "x"); + u = c_emlrt_marshallIn(&st, emlrtAlias(prhs[1]), "u"); + /* Invoke the target function */ + quadcopter_model(*x, *u, *dxdt); + /* Marshall function outputs */ + *plhs = emlrt_marshallOut(*dxdt); +} + +void quadcopter_model_atexit(void) +{ + emlrtStack st = { + NULL, /* site */ + NULL, /* tls */ + NULL /* prev */ + }; + mexFunctionCreateRootTLS(); + st.tls = emlrtRootTLSGlobal; + emlrtPushHeapReferenceStackR2021a( + &st, false, NULL, (void *)&emlrtExitTimeCleanupDtorFcn, NULL, NULL, NULL); + emlrtEnterRtStackR2012b(&st); + emlrtDestroyRootTLS(&emlrtRootTLSGlobal); + quadcopter_model_xil_terminate(); + quadcopter_model_xil_shutdown(); + emlrtExitTimeCleanup(&emlrtContextGlobal); +} + +void quadcopter_model_initialize(void) +{ + emlrtStack st = { + NULL, /* site */ + NULL, /* tls */ + NULL /* prev */ + }; + mexFunctionCreateRootTLS(); + st.tls = emlrtRootTLSGlobal; + emlrtClearAllocCountR2012b(&st, false, 0U, NULL); + emlrtEnterRtStackR2012b(&st); + emlrtFirstTimeR2012b(emlrtRootTLSGlobal); +} + +void quadcopter_model_terminate(void) +{ + emlrtDestroyRootTLS(&emlrtRootTLSGlobal); +} + +/* End of code generation (_coder_quadcopter_model_api.c) */ diff --git a/matlabfiles/codegen/lib/quadcopter_model/interface/_coder_quadcopter_model_api.h b/matlabfiles/codegen/lib/quadcopter_model/interface/_coder_quadcopter_model_api.h new file mode 100644 index 0000000..949b994 --- /dev/null +++ b/matlabfiles/codegen/lib/quadcopter_model/interface/_coder_quadcopter_model_api.h @@ -0,0 +1,49 @@ +/* + * Academic License - for use in teaching, academic research, and meeting + * course requirements at degree granting institutions only. Not for + * government, commercial, or other organizational use. + * + * _coder_quadcopter_model_api.h + * + * Code generation for function 'quadcopter_model' + * + */ + +#ifndef _CODER_QUADCOPTER_MODEL_API_H +#define _CODER_QUADCOPTER_MODEL_API_H + +/* Include files */ +#include "emlrt.h" +#include "mex.h" +#include "tmwtypes.h" +#include + +/* Variable Declarations */ +extern emlrtCTX emlrtRootTLSGlobal; +extern emlrtContext emlrtContextGlobal; + +#ifdef __cplusplus +extern "C" { +#endif + +/* Function Declarations */ +void quadcopter_model(real_T x[12], real_T u[4], real_T dxdt[12]); + +void quadcopter_model_api(const mxArray *const prhs[2], const mxArray **plhs); + +void quadcopter_model_atexit(void); + +void quadcopter_model_initialize(void); + +void quadcopter_model_terminate(void); + +void quadcopter_model_xil_shutdown(void); + +void quadcopter_model_xil_terminate(void); + +#ifdef __cplusplus +} +#endif + +#endif +/* End of code generation (_coder_quadcopter_model_api.h) */ diff --git a/matlabfiles/codegen/lib/quadcopter_model/interface/_coder_quadcopter_model_info.c b/matlabfiles/codegen/lib/quadcopter_model/interface/_coder_quadcopter_model_info.c new file mode 100644 index 0000000..c0c9902 --- /dev/null +++ b/matlabfiles/codegen/lib/quadcopter_model/interface/_coder_quadcopter_model_info.c @@ -0,0 +1,80 @@ +/* + * Academic License - for use in teaching, academic research, and meeting + * course requirements at degree granting institutions only. Not for + * government, commercial, or other organizational use. + * + * _coder_quadcopter_model_info.c + * + * Code generation for function 'quadcopter_model' + * + */ + +/* Include files */ +#include "_coder_quadcopter_model_info.h" +#include "emlrt.h" +#include "tmwtypes.h" + +/* Function Declarations */ +static const mxArray *c_emlrtMexFcnResolvedFunctionsI(void); + +/* Function Definitions */ +static const mxArray *c_emlrtMexFcnResolvedFunctionsI(void) +{ + static const int32_T iv[2] = {0, 1}; + const mxArray *m; + const mxArray *nameCaptureInfo; + nameCaptureInfo = NULL; + m = emlrtCreateNumericArray(2, (const void *)&iv[0], mxDOUBLE_CLASS, mxREAL); + emlrtAssign(&nameCaptureInfo, m); + return nameCaptureInfo; +} + +mxArray *emlrtMexFcnProperties(void) +{ + mxArray *xEntryPoints; + mxArray *xInputs; + mxArray *xResult; + const char_T *propFieldName[9] = {"Version", + "ResolvedFunctions", + "Checksum", + "EntryPoints", + "CoverageInfo", + "IsPolymorphic", + "PropertyList", + "UUID", + "ClassEntryPointIsHandle"}; + const char_T *epFieldName[8] = { + "Name", "NumberOfInputs", "NumberOfOutputs", "ConstantInputs", + "FullPath", "TimeStamp", "Constructor", "Visible"}; + xEntryPoints = + emlrtCreateStructMatrix(1, 1, 8, (const char_T **)&epFieldName[0]); + xInputs = emlrtCreateLogicalMatrix(1, 2); + emlrtSetField(xEntryPoints, 0, "Name", + emlrtMxCreateString("quadcopter_model")); + emlrtSetField(xEntryPoints, 0, "NumberOfInputs", + emlrtMxCreateDoubleScalar(2.0)); + emlrtSetField(xEntryPoints, 0, "NumberOfOutputs", + emlrtMxCreateDoubleScalar(1.0)); + emlrtSetField(xEntryPoints, 0, "ConstantInputs", xInputs); + emlrtSetField(xEntryPoints, 0, "FullPath", + emlrtMxCreateString( + "C:\\Users\\baudo\\Desktop\\ST " + "DDC\\dronecontrol\\matlabfiles\\quadcopter_model.m")); + emlrtSetField(xEntryPoints, 0, "TimeStamp", + emlrtMxCreateDoubleScalar(739926.46809027775)); + emlrtSetField(xEntryPoints, 0, "Constructor", + emlrtMxCreateLogicalScalar(false)); + emlrtSetField(xEntryPoints, 0, "Visible", emlrtMxCreateLogicalScalar(true)); + xResult = + emlrtCreateStructMatrix(1, 1, 9, (const char_T **)&propFieldName[0]); + emlrtSetField(xResult, 0, "Version", + emlrtMxCreateString("24.1.0.2837808 (R2024a) Update 7")); + emlrtSetField(xResult, 0, "ResolvedFunctions", + (mxArray *)c_emlrtMexFcnResolvedFunctionsI()); + emlrtSetField(xResult, 0, "Checksum", + emlrtMxCreateString("UZNV6GSDjNHqUjgyPa3PcG")); + emlrtSetField(xResult, 0, "EntryPoints", xEntryPoints); + return xResult; +} + +/* End of code generation (_coder_quadcopter_model_info.c) */ diff --git a/matlabfiles/codegen/lib/quadcopter_model/interface/_coder_quadcopter_model_info.h b/matlabfiles/codegen/lib/quadcopter_model/interface/_coder_quadcopter_model_info.h new file mode 100644 index 0000000..d18a8e0 --- /dev/null +++ b/matlabfiles/codegen/lib/quadcopter_model/interface/_coder_quadcopter_model_info.h @@ -0,0 +1,30 @@ +/* + * Academic License - for use in teaching, academic research, and meeting + * course requirements at degree granting institutions only. Not for + * government, commercial, or other organizational use. + * + * _coder_quadcopter_model_info.h + * + * Code generation for function 'quadcopter_model' + * + */ + +#ifndef _CODER_QUADCOPTER_MODEL_INFO_H +#define _CODER_QUADCOPTER_MODEL_INFO_H + +/* Include files */ +#include "mex.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Function Declarations */ +MEXFUNCTION_LINKAGE mxArray *emlrtMexFcnProperties(void); + +#ifdef __cplusplus +} +#endif + +#endif +/* End of code generation (_coder_quadcopter_model_info.h) */ diff --git a/matlabfiles/codegen/lib/quadcopter_model/interface/_coder_quadcopter_model_mex.c b/matlabfiles/codegen/lib/quadcopter_model/interface/_coder_quadcopter_model_mex.c new file mode 100644 index 0000000..fe018d7 --- /dev/null +++ b/matlabfiles/codegen/lib/quadcopter_model/interface/_coder_quadcopter_model_mex.c @@ -0,0 +1,64 @@ +/* + * Academic License - for use in teaching, academic research, and meeting + * course requirements at degree granting institutions only. Not for + * government, commercial, or other organizational use. + * + * _coder_quadcopter_model_mex.c + * + * Code generation for function 'quadcopter_model' + * + */ + +/* Include files */ +#include "_coder_quadcopter_model_mex.h" +#include "_coder_quadcopter_model_api.h" + +/* Function Definitions */ +void mexFunction(int32_T nlhs, mxArray *plhs[], int32_T nrhs, + const mxArray *prhs[]) +{ + mexAtExit(&quadcopter_model_atexit); + /* Module initialization. */ + quadcopter_model_initialize(); + /* Dispatch the entry-point. */ + unsafe_quadcopter_model_mexFunction(nlhs, plhs, nrhs, prhs); + /* Module termination. */ + quadcopter_model_terminate(); +} + +emlrtCTX mexFunctionCreateRootTLS(void) +{ + emlrtCreateRootTLSR2022a(&emlrtRootTLSGlobal, &emlrtContextGlobal, NULL, 1, + NULL, "windows-1252", true); + return emlrtRootTLSGlobal; +} + +void unsafe_quadcopter_model_mexFunction(int32_T nlhs, mxArray *plhs[1], + int32_T nrhs, const mxArray *prhs[2]) +{ + emlrtStack st = { + NULL, /* site */ + NULL, /* tls */ + NULL /* prev */ + }; + const mxArray *b_prhs[2]; + const mxArray *outputs; + st.tls = emlrtRootTLSGlobal; + /* Check for proper number of arguments. */ + if (nrhs != 2) { + emlrtErrMsgIdAndTxt(&st, "EMLRT:runTime:WrongNumberOfInputs", 5, 12, 2, 4, + 16, "quadcopter_model"); + } + if (nlhs > 1) { + emlrtErrMsgIdAndTxt(&st, "EMLRT:runTime:TooManyOutputArguments", 3, 4, 16, + "quadcopter_model"); + } + /* Call the function. */ + b_prhs[0] = prhs[0]; + b_prhs[1] = prhs[1]; + quadcopter_model_api(b_prhs, &outputs); + /* Copy over outputs to the caller. */ + emlrtReturnArrays(1, &plhs[0], &outputs); +} + +/* End of code generation (_coder_quadcopter_model_mex.c) */ diff --git a/matlabfiles/codegen/lib/quadcopter_model/interface/_coder_quadcopter_model_mex.h b/matlabfiles/codegen/lib/quadcopter_model/interface/_coder_quadcopter_model_mex.h new file mode 100644 index 0000000..cb6ef7e --- /dev/null +++ b/matlabfiles/codegen/lib/quadcopter_model/interface/_coder_quadcopter_model_mex.h @@ -0,0 +1,38 @@ +/* + * Academic License - for use in teaching, academic research, and meeting + * course requirements at degree granting institutions only. Not for + * government, commercial, or other organizational use. + * + * _coder_quadcopter_model_mex.h + * + * Code generation for function 'quadcopter_model' + * + */ + +#ifndef _CODER_QUADCOPTER_MODEL_MEX_H +#define _CODER_QUADCOPTER_MODEL_MEX_H + +/* Include files */ +#include "emlrt.h" +#include "mex.h" +#include "tmwtypes.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Function Declarations */ +MEXFUNCTION_LINKAGE void mexFunction(int32_T nlhs, mxArray *plhs[], + int32_T nrhs, const mxArray *prhs[]); + +emlrtCTX mexFunctionCreateRootTLS(void); + +void unsafe_quadcopter_model_mexFunction(int32_T nlhs, mxArray *plhs[1], + int32_T nrhs, const mxArray *prhs[2]); + +#ifdef __cplusplus +} +#endif + +#endif +/* End of code generation (_coder_quadcopter_model_mex.h) */ diff --git a/matlabfiles/codegen/lib/quadcopter_model/libquadcopter_model.so b/matlabfiles/codegen/lib/quadcopter_model/libquadcopter_model.so new file mode 100755 index 0000000..a7caf7d Binary files /dev/null and b/matlabfiles/codegen/lib/quadcopter_model/libquadcopter_model.so differ diff --git a/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model.c b/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model.c new file mode 100644 index 0000000..9f5027d --- /dev/null +++ b/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model.c @@ -0,0 +1,173 @@ +/* + * Academic License - for use in teaching, academic research, and meeting + * course requirements at degree granting institutions only. Not for + * government, commercial, or other organizational use. + * + * quadcopter_model.c + * + * Code generation for function 'quadcopter_model' + * + */ + +/* Include files */ +#include "quadcopter_model.h" +#include +#include + +/* Function Definitions */ +void quadcopter_model(const double x[12], const double u[4], double dxdt[12]) +{ + static const double b_a[9] = {0.1, 0.0, 0.0, 0.0, 0.1, 0.0, 0.0, 0.0, 0.2}; + static const double dv[3] = {0.0, 0.0, 9.81}; + static const signed char c_a[9] = {10, 0, 0, 0, 10, 0, 0, 0, 5}; + double c_R_y_tmp[9]; + double c_R_z_tmp[9]; + double d_R_z_tmp[9]; + double accel[3]; + double angular_acc[3]; + double b_u[3]; + double y[3]; + double R_x_tmp; + double R_y_tmp; + double R_z_tmp; + double a; + double b_R_x_tmp; + double b_R_y_tmp; + double b_R_z_tmp; + double d; + int R_z_tmp_tmp; + int i; + int i1; + /* Parameters of the quadcopter */ + /* Mass of the quadcopter (kg) */ + /* Gravity (m/s^2) */ + /* Inertia matrix (kg*m^2) */ + /* Distance from center of mass to motors (m) */ + /* Thrust constant (N per unit input) */ + /* Moment constant (N*m per unit input) */ + /* Quadcopter state vector */ + /* Position (x, y, z) */ + /* Velocity (vx, vy, vz) */ + /* Euler angles (phi, theta, psi) */ + /* Angular velocity (wx, wy, wz) */ + /* Rotation matrix (from global frame to body frame) */ + /* Function to convert Euler angles to rotation matrix */ + /* Roll */ + /* Pitch */ + /* Yaw */ + /* Rotation matrix about x-axis (roll) */ + R_x_tmp = sin(x[6]); + b_R_x_tmp = cos(x[6]); + /* Rotation matrix about y-axis (pitch) */ + R_y_tmp = sin(x[7]); + b_R_y_tmp = cos(x[7]); + /* Rotation matrix about z-axis (yaw) */ + R_z_tmp = sin(x[8]); + b_R_z_tmp = cos(x[8]); + /* Combined rotation matrix: R = Rz * Ry * Rx */ + /* Motor forces (thrust and torques) */ + /* Thrust from motor 1 */ + /* Thrust from motor 2 */ + /* Thrust from motor 3 */ + /* Thrust from motor 4 */ + /* Compute total thrust from all motors */ + a = ((u[0] + u[1]) + u[2]) + u[3]; + /* Total thrust force */ + /* Compute torques generated by the motors */ + /* Roll torque */ + /* Pitch torque */ + /* Yaw torque */ + /* Total thrust force vector in world frame (aligned with body z-axis) */ + /* Thrust direction (z-axis of body) */ + /* Translational dynamics (acceleration) */ + c_R_z_tmp[0] = b_R_z_tmp; + c_R_z_tmp[3] = -R_z_tmp; + c_R_z_tmp[6] = 0.0; + c_R_z_tmp[1] = R_z_tmp; + c_R_z_tmp[4] = b_R_z_tmp; + c_R_z_tmp[7] = 0.0; + c_R_y_tmp[0] = b_R_y_tmp; + c_R_y_tmp[3] = 0.0; + c_R_y_tmp[6] = R_y_tmp; + c_R_z_tmp[2] = 0.0; + c_R_y_tmp[1] = 0.0; + c_R_z_tmp[5] = 0.0; + c_R_y_tmp[4] = 1.0; + c_R_z_tmp[8] = 1.0; + c_R_y_tmp[7] = 0.0; + c_R_y_tmp[2] = -R_y_tmp; + c_R_y_tmp[5] = 0.0; + c_R_y_tmp[8] = b_R_y_tmp; + for (i = 0; i < 3; i++) { + b_R_z_tmp = c_R_z_tmp[i]; + d = c_R_z_tmp[i + 3]; + i1 = (int)c_R_z_tmp[i + 6]; + for (R_z_tmp_tmp = 0; R_z_tmp_tmp < 3; R_z_tmp_tmp++) { + d_R_z_tmp[i + 3 * R_z_tmp_tmp] = + (b_R_z_tmp * c_R_y_tmp[3 * R_z_tmp_tmp] + + d * c_R_y_tmp[3 * R_z_tmp_tmp + 1]) + + (double)i1 * c_R_y_tmp[3 * R_z_tmp_tmp + 2]; + } + } + c_R_y_tmp[0] = 1.0; + c_R_y_tmp[3] = 0.0; + c_R_y_tmp[6] = 0.0; + c_R_y_tmp[1] = 0.0; + c_R_y_tmp[4] = b_R_x_tmp; + c_R_y_tmp[7] = -R_x_tmp; + c_R_y_tmp[2] = 0.0; + c_R_y_tmp[5] = R_x_tmp; + c_R_y_tmp[8] = b_R_x_tmp; + /* Acceleration = thrust/mass - gravity */ + /* Rotational dynamics (angular acceleration) */ + b_u[0] = u[1] - u[3]; + b_u[1] = u[2] - u[0]; + b_u[2] = ((u[0] - u[1]) + u[2]) - u[3]; + for (i = 0; i < 3; i++) { + b_R_z_tmp = d_R_z_tmp[i]; + d = d_R_z_tmp[i + 3]; + R_y_tmp = d_R_z_tmp[i + 6]; + b_R_y_tmp = 0.0; + R_z_tmp = 0.0; + for (i1 = 0; i1 < 3; i1++) { + R_z_tmp_tmp = i + 3 * i1; + c_R_z_tmp[R_z_tmp_tmp] = + (b_R_z_tmp * c_R_y_tmp[3 * i1] + d * c_R_y_tmp[3 * i1 + 1]) + + R_y_tmp * c_R_y_tmp[3 * i1 + 2]; + b_R_y_tmp += b_a[R_z_tmp_tmp] * x[i1 + 9]; + R_z_tmp += (double)c_a[R_z_tmp_tmp] * b_u[i1]; + } + angular_acc[i] = R_z_tmp; + y[i] = b_R_y_tmp; + accel[i] = a * c_R_z_tmp[i + 6] / 1.5 - dv[i]; + } + __m128d r; + __m128d r1; + b_u[0] = y[2] * x[10] - y[1] * x[11]; + b_u[1] = y[0] * x[11] - y[2] * x[9]; + b_u[2] = y[1] * x[9] - y[0] * x[10]; + r = _mm_loadu_pd(&angular_acc[0]); + r1 = _mm_loadu_pd(&b_u[0]); + _mm_storeu_pd(&angular_acc[0], _mm_sub_pd(r, r1)); + angular_acc[2] -= b_u[2]; + /* Angular acceleration = torque/inertia - gyroscopic effects */ + /* Compute state derivatives */ + /* Derivative of position is velocity */ + /* Derivative of velocity is acceleration */ + /* Derivative of Euler angles is angular velocity */ + dxdt[0] = x[3]; + dxdt[3] = accel[0]; + dxdt[6] = x[9]; + dxdt[9] = angular_acc[0]; + dxdt[1] = x[4]; + dxdt[4] = accel[1]; + dxdt[7] = x[10]; + dxdt[10] = angular_acc[1]; + dxdt[2] = x[5]; + dxdt[5] = accel[2]; + dxdt[8] = x[11]; + dxdt[11] = angular_acc[2]; + /* Derivative of angular velocity is angular acceleration */ +} + +/* End of code generation (quadcopter_model.c) */ diff --git a/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model.h b/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model.h new file mode 100644 index 0000000..6319bad --- /dev/null +++ b/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model.h @@ -0,0 +1,33 @@ +/* + * Academic License - for use in teaching, academic research, and meeting + * course requirements at degree granting institutions only. Not for + * government, commercial, or other organizational use. + * + * quadcopter_model.h + * + * Code generation for function 'quadcopter_model' + * + */ + +#ifndef QUADCOPTER_MODEL_H +#define QUADCOPTER_MODEL_H + +/* Include files */ +#include "rtwtypes.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Function Declarations */ +extern void quadcopter_model(const double x[12], const double u[4], + double dxdt[12]); + +#ifdef __cplusplus +} +#endif + +#endif +/* End of code generation (quadcopter_model.h) */ diff --git a/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model.lib b/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model.lib new file mode 100644 index 0000000..d872837 Binary files /dev/null and b/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model.lib differ diff --git a/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model.obj b/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model.obj new file mode 100644 index 0000000..097edc8 Binary files /dev/null and b/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model.obj differ diff --git a/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model_data.h b/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model_data.h new file mode 100644 index 0000000..bfcd75c --- /dev/null +++ b/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model_data.h @@ -0,0 +1,21 @@ +/* + * Academic License - for use in teaching, academic research, and meeting + * course requirements at degree granting institutions only. Not for + * government, commercial, or other organizational use. + * + * quadcopter_model_data.h + * + * Code generation for function 'quadcopter_model_data' + * + */ + +#ifndef QUADCOPTER_MODEL_DATA_H +#define QUADCOPTER_MODEL_DATA_H + +/* Include files */ +#include "rtwtypes.h" +#include +#include + +#endif +/* End of code generation (quadcopter_model_data.h) */ diff --git a/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model_initialize.c b/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model_initialize.c new file mode 100644 index 0000000..c2d418f --- /dev/null +++ b/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model_initialize.c @@ -0,0 +1,20 @@ +/* + * Academic License - for use in teaching, academic research, and meeting + * course requirements at degree granting institutions only. Not for + * government, commercial, or other organizational use. + * + * quadcopter_model_initialize.c + * + * Code generation for function 'quadcopter_model_initialize' + * + */ + +/* Include files */ +#include "quadcopter_model_initialize.h" + +/* Function Definitions */ +void quadcopter_model_initialize(void) +{ +} + +/* End of code generation (quadcopter_model_initialize.c) */ diff --git a/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model_initialize.h b/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model_initialize.h new file mode 100644 index 0000000..4fb5430 --- /dev/null +++ b/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model_initialize.h @@ -0,0 +1,32 @@ +/* + * Academic License - for use in teaching, academic research, and meeting + * course requirements at degree granting institutions only. Not for + * government, commercial, or other organizational use. + * + * quadcopter_model_initialize.h + * + * Code generation for function 'quadcopter_model_initialize' + * + */ + +#ifndef QUADCOPTER_MODEL_INITIALIZE_H +#define QUADCOPTER_MODEL_INITIALIZE_H + +/* Include files */ +#include "rtwtypes.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Function Declarations */ +extern void quadcopter_model_initialize(void); + +#ifdef __cplusplus +} +#endif + +#endif +/* End of code generation (quadcopter_model_initialize.h) */ diff --git a/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model_initialize.obj b/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model_initialize.obj new file mode 100644 index 0000000..ba55c29 Binary files /dev/null and b/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model_initialize.obj differ diff --git a/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model_rtw.bat b/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model_rtw.bat new file mode 100644 index 0000000..ae0cc0c --- /dev/null +++ b/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model_rtw.bat @@ -0,0 +1,17 @@ +@echo off + +set skipSetupArg=%2 +if "%skipSetupArg%" NEQ "skip_setup_msvc" ( +call "setup_msvc.bat" +) + +cd . + +if "%1"=="" (nmake -f quadcopter_model_rtw.mk all) else (nmake -f quadcopter_model_rtw.mk %1) +@if errorlevel 1 goto error_exit + +exit /B 0 + +:error_exit +echo The make command returned an error of %errorlevel% +exit /B 1 \ No newline at end of file diff --git a/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model_rtw.mk b/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model_rtw.mk new file mode 100644 index 0000000..0d20e45 --- /dev/null +++ b/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model_rtw.mk @@ -0,0 +1,416 @@ +########################################################################### +## Makefile generated for component 'quadcopter_model'. +## +## Makefile : quadcopter_model_rtw.mk +## Generated on : Wed Nov 05 13:15:26 2025 +## Final product: .\quadcopter_model.lib +## Product type : static-library +## +########################################################################### + +########################################################################### +## MACROS +########################################################################### + +# Macro Descriptions: +# PRODUCT_NAME Name of the system to build +# MAKEFILE Name of this makefile +# COMPILER_COMMAND_FILE Compiler command listing model reference header paths +# CMD_FILE Command file +# MODELLIB Static library target + +PRODUCT_NAME = quadcopter_model +MAKEFILE = quadcopter_model_rtw.mk +MATLAB_ROOT = C:\PROGRA~1\MATLAB\R2024a +MATLAB_BIN = C:\PROGRA~1\MATLAB\R2024a\bin +MATLAB_ARCH_BIN = $(MATLAB_BIN)\win64 +START_DIR = C:\Users\baudo\Desktop\STDDC~1\DRONEC~1\MATLAB~1 +TGT_FCN_LIB = ISO_C +SOLVER_OBJ = +CLASSIC_INTERFACE = 0 +MODEL_HAS_DYNAMICALLY_LOADED_SFCNS = +RELATIVE_PATH_TO_ANCHOR = ..\..\.. +COMPILER_COMMAND_FILE = quadcopter_model_rtw_comp.rsp +CMD_FILE = quadcopter_model_rtw.rsp +C_STANDARD_OPTS = +CPP_STANDARD_OPTS = +NODEBUG = 1 +MODELLIB = quadcopter_model.lib + +########################################################################### +## TOOLCHAIN SPECIFICATIONS +########################################################################### + +# Toolchain Name: Microsoft Visual C++ 2022 v17.0 | nmake (64-bit Windows) +# Supported Version(s): 17.0 +# ToolchainInfo Version: 2024a +# Specification Revision: 1.0 +# +#------------------------------------------- +# Macros assumed to be defined elsewhere +#------------------------------------------- + +# NODEBUG +# cvarsdll +# cvarsmt +# conlibsmt +# ldebug +# conflags +# cflags + +#----------- +# MACROS +#----------- + +MW_EXTERNLIB_DIR = $(MATLAB_ROOT)\extern\lib\win64\microsoft +MW_LIB_DIR = $(MATLAB_ROOT)\lib\win64 +CPU = AMD64 +APPVER = 5.02 +CVARSFLAG = $(cvarsmt) +CFLAGS_ADDITIONAL = -D_CRT_SECURE_NO_WARNINGS +CPPFLAGS_ADDITIONAL = -EHs -D_CRT_SECURE_NO_WARNINGS /wd4251 /Zc:__cplusplus +LIBS_TOOLCHAIN = $(conlibs) + +TOOLCHAIN_SRCS = +TOOLCHAIN_INCS = +TOOLCHAIN_LIBS = + +#------------------------ +# BUILD TOOL COMMANDS +#------------------------ + +# C Compiler: Microsoft Visual C Compiler +CC = cl + +# Linker: Microsoft Visual C Linker +LD = link + +# C++ Compiler: Microsoft Visual C++ Compiler +CPP = cl + +# C++ Linker: Microsoft Visual C++ Linker +CPP_LD = link + +# Archiver: Microsoft Visual C/C++ Archiver +AR = lib + +# MEX Tool: MEX Tool +MEX_PATH = $(MATLAB_ARCH_BIN) +MEX = "$(MEX_PATH)\mex" + +# Download: Download +DOWNLOAD = + +# Execute: Execute +EXECUTE = $(PRODUCT) + +# Builder: NMAKE Utility +MAKE = nmake + + +#------------------------- +# Directives/Utilities +#------------------------- + +CDEBUG = -Zi +C_OUTPUT_FLAG = -Fo +LDDEBUG = /DEBUG +OUTPUT_FLAG = -out: +CPPDEBUG = -Zi +CPP_OUTPUT_FLAG = -Fo +CPPLDDEBUG = /DEBUG +OUTPUT_FLAG = -out: +ARDEBUG = +STATICLIB_OUTPUT_FLAG = -out: +MEX_DEBUG = -g +RM = @del +ECHO = @echo +MV = @ren +RUN = @cmd /C + +#-------------------------------------- +# "Faster Runs" Build Configuration +#-------------------------------------- + +ARFLAGS = /nologo +CFLAGS = $(cflags) $(CVARSFLAG) $(CFLAGS_ADDITIONAL) \ + /O2 /Oy- +CPPFLAGS = /TP $(cflags) $(CVARSFLAG) $(CPPFLAGS_ADDITIONAL) \ + /O2 /Oy- +CPP_LDFLAGS = $(ldebug) $(conflags) $(LIBS_TOOLCHAIN) +CPP_SHAREDLIB_LDFLAGS = $(ldebug) $(conflags) $(LIBS_TOOLCHAIN) \ + -dll -def:$(DEF_FILE) +DOWNLOAD_FLAGS = +EXECUTE_FLAGS = +LDFLAGS = $(ldebug) $(conflags) $(LIBS_TOOLCHAIN) +MEX_CPPFLAGS = +MEX_CPPLDFLAGS = +MEX_CFLAGS = +MEX_LDFLAGS = +MAKE_FLAGS = -f $(MAKEFILE) +SHAREDLIB_LDFLAGS = $(ldebug) $(conflags) $(LIBS_TOOLCHAIN) \ + -dll -def:$(DEF_FILE) + + + +########################################################################### +## OUTPUT INFO +########################################################################### + +PRODUCT = .\quadcopter_model.lib +PRODUCT_TYPE = "static-library" +BUILD_TYPE = "Static Library" + +########################################################################### +## INCLUDE PATHS +########################################################################### + +INCLUDES_BUILDINFO = + +INCLUDES = $(INCLUDES_BUILDINFO) + +########################################################################### +## DEFINES +########################################################################### + +DEFINES_CUSTOM = +DEFINES_STANDARD = -DMODEL=quadcopter_model + +DEFINES = $(DEFINES_CUSTOM) $(DEFINES_STANDARD) + +########################################################################### +## SOURCE FILES +########################################################################### + +SRCS = $(START_DIR)\codegen\lib\quadcopter_model\quadcopter_model_initialize.c $(START_DIR)\codegen\lib\quadcopter_model\quadcopter_model_terminate.c $(START_DIR)\codegen\lib\quadcopter_model\quadcopter_model.c + +ALL_SRCS = $(SRCS) + +########################################################################### +## OBJECTS +########################################################################### + +OBJS = quadcopter_model_initialize.obj quadcopter_model_terminate.obj quadcopter_model.obj + +ALL_OBJS = $(OBJS) + +########################################################################### +## PREBUILT OBJECT FILES +########################################################################### + +PREBUILT_OBJS = + +########################################################################### +## LIBRARIES +########################################################################### + +LIBS = + +########################################################################### +## SYSTEM LIBRARIES +########################################################################### + +SYSTEM_LIBS = + +########################################################################### +## ADDITIONAL TOOLCHAIN FLAGS +########################################################################### + +#--------------- +# C Compiler +#--------------- + +CFLAGS_ = /source-charset:utf-8 +CFLAGS_BASIC = $(DEFINES) @$(COMPILER_COMMAND_FILE) + +CFLAGS = $(CFLAGS) $(CFLAGS_) $(CFLAGS_BASIC) + +#----------------- +# C++ Compiler +#----------------- + +CPPFLAGS_ = /source-charset:utf-8 +CPPFLAGS_BASIC = $(DEFINES) @$(COMPILER_COMMAND_FILE) + +CPPFLAGS = $(CPPFLAGS) $(CPPFLAGS_) $(CPPFLAGS_BASIC) + +########################################################################### +## INLINED COMMANDS +########################################################################### + + +!include $(MATLAB_ROOT)\rtw\c\tools\vcdefs.mak + + +########################################################################### +## PHONY TARGETS +########################################################################### + +.PHONY : all build clean info prebuild download execute set_environment_variables + + +all : build + @cmd /C "@echo ### Successfully generated all binary outputs." + + +build : set_environment_variables prebuild $(PRODUCT) + + +prebuild : + + +download : $(PRODUCT) + + +execute : download + + +set_environment_variables : + @set INCLUDE=$(INCLUDES);$(INCLUDE) + @set LIB=$(LIB) + + +########################################################################### +## FINAL TARGET +########################################################################### + +#--------------------------------- +# Create a static library +#--------------------------------- + +$(PRODUCT) : $(OBJS) $(PREBUILT_OBJS) + @cmd /C "@echo ### Creating static library "$(PRODUCT)" ..." + $(AR) $(ARFLAGS) -out:$(PRODUCT) @$(CMD_FILE) + @cmd /C "@echo ### Created: $(PRODUCT)" + + +########################################################################### +## INTERMEDIATE TARGETS +########################################################################### + +#--------------------- +# SOURCE-TO-OBJECT +#--------------------- + +.c.obj : + $(CC) $(CFLAGS) -Fo"$@" "$<" + + +.cpp.obj : + $(CPP) $(CPPFLAGS) -Fo"$@" "$<" + + +.cc.obj : + $(CPP) $(CPPFLAGS) -Fo"$@" "$<" + + +.cxx.obj : + $(CPP) $(CPPFLAGS) -Fo"$@" "$<" + + +{$(RELATIVE_PATH_TO_ANCHOR)}.c.obj : + $(CC) $(CFLAGS) -Fo"$@" "$<" + + +{$(RELATIVE_PATH_TO_ANCHOR)}.cpp.obj : + $(CPP) $(CPPFLAGS) -Fo"$@" "$<" + + +{$(RELATIVE_PATH_TO_ANCHOR)}.cc.obj : + $(CPP) $(CPPFLAGS) -Fo"$@" "$<" + + +{$(RELATIVE_PATH_TO_ANCHOR)}.cxx.obj : + $(CPP) $(CPPFLAGS) -Fo"$@" "$<" + + +{$(START_DIR)\codegen\lib\quadcopter_model}.c.obj : + $(CC) $(CFLAGS) -Fo"$@" "$<" + + +{$(START_DIR)\codegen\lib\quadcopter_model}.cpp.obj : + $(CPP) $(CPPFLAGS) -Fo"$@" "$<" + + +{$(START_DIR)\codegen\lib\quadcopter_model}.cc.obj : + $(CPP) $(CPPFLAGS) -Fo"$@" "$<" + + +{$(START_DIR)\codegen\lib\quadcopter_model}.cxx.obj : + $(CPP) $(CPPFLAGS) -Fo"$@" "$<" + + +{$(START_DIR)}.c.obj : + $(CC) $(CFLAGS) -Fo"$@" "$<" + + +{$(START_DIR)}.cpp.obj : + $(CPP) $(CPPFLAGS) -Fo"$@" "$<" + + +{$(START_DIR)}.cc.obj : + $(CPP) $(CPPFLAGS) -Fo"$@" "$<" + + +{$(START_DIR)}.cxx.obj : + $(CPP) $(CPPFLAGS) -Fo"$@" "$<" + + +quadcopter_model_initialize.obj : "$(START_DIR)\codegen\lib\quadcopter_model\quadcopter_model_initialize.c" + $(CC) $(CFLAGS) -Fo"$@" "$(START_DIR)\codegen\lib\quadcopter_model\quadcopter_model_initialize.c" + + +quadcopter_model_terminate.obj : "$(START_DIR)\codegen\lib\quadcopter_model\quadcopter_model_terminate.c" + $(CC) $(CFLAGS) -Fo"$@" "$(START_DIR)\codegen\lib\quadcopter_model\quadcopter_model_terminate.c" + + +quadcopter_model.obj : "$(START_DIR)\codegen\lib\quadcopter_model\quadcopter_model.c" + $(CC) $(CFLAGS) -Fo"$@" "$(START_DIR)\codegen\lib\quadcopter_model\quadcopter_model.c" + + +########################################################################### +## DEPENDENCIES +########################################################################### + +$(ALL_OBJS) : rtw_proj.tmw $(COMPILER_COMMAND_FILE) $(MAKEFILE) + + +########################################################################### +## MISCELLANEOUS TARGETS +########################################################################### + +info : + @cmd /C "@echo ### PRODUCT = $(PRODUCT)" + @cmd /C "@echo ### PRODUCT_TYPE = $(PRODUCT_TYPE)" + @cmd /C "@echo ### BUILD_TYPE = $(BUILD_TYPE)" + @cmd /C "@echo ### INCLUDES = $(INCLUDES)" + @cmd /C "@echo ### DEFINES = $(DEFINES)" + @cmd /C "@echo ### ALL_SRCS = $(ALL_SRCS)" + @cmd /C "@echo ### ALL_OBJS = $(ALL_OBJS)" + @cmd /C "@echo ### LIBS = $(LIBS)" + @cmd /C "@echo ### MODELREF_LIBS = $(MODELREF_LIBS)" + @cmd /C "@echo ### SYSTEM_LIBS = $(SYSTEM_LIBS)" + @cmd /C "@echo ### TOOLCHAIN_LIBS = $(TOOLCHAIN_LIBS)" + @cmd /C "@echo ### CFLAGS = $(CFLAGS)" + @cmd /C "@echo ### LDFLAGS = $(LDFLAGS)" + @cmd /C "@echo ### SHAREDLIB_LDFLAGS = $(SHAREDLIB_LDFLAGS)" + @cmd /C "@echo ### CPPFLAGS = $(CPPFLAGS)" + @cmd /C "@echo ### CPP_LDFLAGS = $(CPP_LDFLAGS)" + @cmd /C "@echo ### CPP_SHAREDLIB_LDFLAGS = $(CPP_SHAREDLIB_LDFLAGS)" + @cmd /C "@echo ### ARFLAGS = $(ARFLAGS)" + @cmd /C "@echo ### MEX_CFLAGS = $(MEX_CFLAGS)" + @cmd /C "@echo ### MEX_CPPFLAGS = $(MEX_CPPFLAGS)" + @cmd /C "@echo ### MEX_LDFLAGS = $(MEX_LDFLAGS)" + @cmd /C "@echo ### MEX_CPPLDFLAGS = $(MEX_CPPLDFLAGS)" + @cmd /C "@echo ### DOWNLOAD_FLAGS = $(DOWNLOAD_FLAGS)" + @cmd /C "@echo ### EXECUTE_FLAGS = $(EXECUTE_FLAGS)" + @cmd /C "@echo ### MAKE_FLAGS = $(MAKE_FLAGS)" + + +clean : + $(ECHO) "### Deleting all derived files ..." + @if exist $(PRODUCT) $(RM) $(PRODUCT) + $(RM) $(ALL_OBJS) + $(ECHO) "### Deleted all derived files." + + diff --git a/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model_rtw.rsp b/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model_rtw.rsp new file mode 100644 index 0000000..87c4954 --- /dev/null +++ b/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model_rtw.rsp @@ -0,0 +1,3 @@ +quadcopter_model_initialize.obj +quadcopter_model_terminate.obj +quadcopter_model.obj diff --git a/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model_rtw_comp.rsp b/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model_rtw_comp.rsp new file mode 100644 index 0000000..10def38 --- /dev/null +++ b/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model_rtw_comp.rsp @@ -0,0 +1,3 @@ +-I"C:\Users\baudo\Desktop\ST DDC\dronecontrol\matlabfiles\codegen\lib\quadcopter_model" +-I"C:\Users\baudo\Desktop\ST DDC\dronecontrol\matlabfiles" +-I"C:\Program Files\MATLAB\R2024a\extern\include" diff --git a/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model_rtw_ref.rsp b/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model_rtw_ref.rsp new file mode 100644 index 0000000..5f28270 --- /dev/null +++ b/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model_rtw_ref.rsp @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model_terminate.c b/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model_terminate.c new file mode 100644 index 0000000..593617b --- /dev/null +++ b/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model_terminate.c @@ -0,0 +1,20 @@ +/* + * Academic License - for use in teaching, academic research, and meeting + * course requirements at degree granting institutions only. Not for + * government, commercial, or other organizational use. + * + * quadcopter_model_terminate.c + * + * Code generation for function 'quadcopter_model_terminate' + * + */ + +/* Include files */ +#include "quadcopter_model_terminate.h" + +/* Function Definitions */ +void quadcopter_model_terminate(void) +{ +} + +/* End of code generation (quadcopter_model_terminate.c) */ diff --git a/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model_terminate.h b/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model_terminate.h new file mode 100644 index 0000000..e790131 --- /dev/null +++ b/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model_terminate.h @@ -0,0 +1,32 @@ +/* + * Academic License - for use in teaching, academic research, and meeting + * course requirements at degree granting institutions only. Not for + * government, commercial, or other organizational use. + * + * quadcopter_model_terminate.h + * + * Code generation for function 'quadcopter_model_terminate' + * + */ + +#ifndef QUADCOPTER_MODEL_TERMINATE_H +#define QUADCOPTER_MODEL_TERMINATE_H + +/* Include files */ +#include "rtwtypes.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Function Declarations */ +extern void quadcopter_model_terminate(void); + +#ifdef __cplusplus +} +#endif + +#endif +/* End of code generation (quadcopter_model_terminate.h) */ diff --git a/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model_terminate.obj b/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model_terminate.obj new file mode 100644 index 0000000..52e7ee6 Binary files /dev/null and b/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model_terminate.obj differ diff --git a/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model_types.h b/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model_types.h new file mode 100644 index 0000000..ebca2b7 --- /dev/null +++ b/matlabfiles/codegen/lib/quadcopter_model/quadcopter_model_types.h @@ -0,0 +1,19 @@ +/* + * Academic License - for use in teaching, academic research, and meeting + * course requirements at degree granting institutions only. Not for + * government, commercial, or other organizational use. + * + * quadcopter_model_types.h + * + * Code generation for function 'quadcopter_model' + * + */ + +#ifndef QUADCOPTER_MODEL_TYPES_H +#define QUADCOPTER_MODEL_TYPES_H + +/* Include files */ +#include "rtwtypes.h" + +#endif +/* End of code generation (quadcopter_model_types.h) */ diff --git a/matlabfiles/codegen/lib/quadcopter_model/rtw_proj.tmw b/matlabfiles/codegen/lib/quadcopter_model/rtw_proj.tmw new file mode 100644 index 0000000..835ba62 --- /dev/null +++ b/matlabfiles/codegen/lib/quadcopter_model/rtw_proj.tmw @@ -0,0 +1 @@ +Code generation project for quadcopter_model using toolchain "Microsoft Visual C++ 2022 v17.0 | nmake (64-bit Windows)". MATLAB root = C:\Program Files\MATLAB\R2024a. diff --git a/matlabfiles/codegen/lib/quadcopter_model/rtwtypes.h b/matlabfiles/codegen/lib/quadcopter_model/rtwtypes.h new file mode 100644 index 0000000..5e1dde1 --- /dev/null +++ b/matlabfiles/codegen/lib/quadcopter_model/rtwtypes.h @@ -0,0 +1,45 @@ +/* + * Academic License - for use in teaching, academic research, and meeting + * course requirements at degree granting institutions only. Not for + * government, commercial, or other organizational use. + * + * rtwtypes.h + * + * Code generation for function 'quadcopter_model' + * + */ + +#ifndef RTWTYPES_H +#define RTWTYPES_H + +/*=======================================================================* + * Fixed width word size data types: * + * int64_T - signed 64 bit integers * + * uint64_T - unsigned 64 bit integers * + *=======================================================================*/ + +#if defined(__APPLE__) +#ifndef INT64_T +#define INT64_T long +#define FMT64 "l" +#if defined(__LP64__) && !defined(INT_TYPE_64_IS_LONG) +#define INT_TYPE_64_IS_LONG +#endif +#endif +#endif + +#if defined(__APPLE__) +#ifndef UINT64_T +#define UINT64_T unsigned long +#define FMT64 "l" +#if defined(__LP64__) && !defined(INT_TYPE_64_IS_LONG) +#define INT_TYPE_64_IS_LONG +#endif +#endif +#endif + +/* Include files */ +#include "tmwtypes.h" + +#endif +/* End of code generation (rtwtypes.h) */ diff --git a/matlabfiles/codegen/lib/quadcopter_model/setup_msvc.bat b/matlabfiles/codegen/lib/quadcopter_model/setup_msvc.bat new file mode 100644 index 0000000..69633d5 --- /dev/null +++ b/matlabfiles/codegen/lib/quadcopter_model/setup_msvc.bat @@ -0,0 +1,2 @@ +set "VSCMD_START_DIR=%CD%" +CALL "C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Auxiliary\Build\VCVARSALL.BAT " amd64 diff --git a/matlabfiles/codegen/lib/quadcopter_model/tmwtypes.h b/matlabfiles/codegen/lib/quadcopter_model/tmwtypes.h new file mode 100644 index 0000000..38a64ed --- /dev/null +++ b/matlabfiles/codegen/lib/quadcopter_model/tmwtypes.h @@ -0,0 +1,17 @@ +#ifndef TMWTYPES_H +#define TMWTYPES_H + +#include +#include + +typedef double real_T; +typedef float real32_T; +typedef int8_t int8_T; +typedef uint8_t uint8_T; +typedef int16_t int16_T; +typedef uint16_t uint16_T; +typedef int32_t int32_T; +typedef uint32_t uint32_T; +typedef bool boolean_T; + +#endif /* TMWTYPES_H */ diff --git a/matlabfiles/quadcopter_build.m b/matlabfiles/quadcopter_build.m new file mode 100644 index 0000000..2abe2e0 --- /dev/null +++ b/matlabfiles/quadcopter_build.m @@ -0,0 +1,85 @@ +%% ======================================================================= +% BUILD SCRIPT for Quadcopter Model — MATLAB Coder +% Generates a DLL (on Windows) +% Author: Solal Baudoin +% ======================================================================= + +clc; +clear; +disp('=== Building quadcopter_model ==='); + +%% 1. Check function visibility +if exist('quadcopter_model.m', 'file') ~= 2 + error('❌ Function "quadcopter_model.m" not found in current directory.'); +end + +%% 2. Check compiler setup +try + mexCompiler = mex.getCompilerConfigurations('C', 'Selected'); + if isempty(mexCompiler) + disp('⚙️ No C compiler configured. Launching mex -setup ...'); + mex -setup + else + fprintf('✅ Using compiler: %s\n', mexCompiler.Name); + end +catch + disp('⚙️ Could not detect compiler. Running setup...'); + mex -setup +end + +%% 3. Configure code generation +cfg = coder.config('lib'); % 'lib' = portable C library +cfg.TargetLang = 'C'; % or 'C++' +cfg.GenerateReport = true; % HTML report +cfg.GenerateExampleMain = 'DoNotGenerate'; % optional + +disp('✅ Configuration ready'); + +%% 4. Define argument types +ARGS = {zeros(12,1), zeros(4,1)}; % State vector, control input + +%% 5. Launch code generation +disp('🚀 Launching MATLAB Coder ...'); +codegen('quadcopter_model', '-config', cfg, '-args', ARGS); + +%% 6. Locate result folder automatically +outDir = fullfile(pwd, 'codegen', 'lib', 'quadcopter_model'); +if ~isfolder(outDir) + warning('⚠️ Could not locate expected output folder.'); +else + disp(['✅ Code generation complete: ', outDir]); +end + +%% 7. Open report automatically +reportFile = fullfile(outDir, 'html', 'index.html'); +if isfile(reportFile) + web(reportFile, '-browser'); +end + +%% 8. (Optional) Build DLL automatically on Windows +if ispc + disp('🧱 Attempting to build DLL with MSVC...'); + cd(outDir); + + % Récupérer tous les .c générés + cFiles = dir('*.c'); + if isempty(cFiles) + error('No C files found in %s', outDir); + end + + % Construire la ligne de commande CL + srcFiles = strjoin({cFiles.name}, ' '); + cmd = sprintf('cl /LD /O2 %s', srcFiles); + [status, cmdout] = system(cmd); + + cd(fullfile(pwd, '..', '..', '..')); % revenir au dossier projet + + if status == 0 + disp('✅ DLL successfully built: codegen/lib/quadcopter_model/quadcopter_model.dll'); + else + disp('⚠️ DLL build failed (check Visual Studio setup).'); + disp(cmdout); + end +end + +disp('=== Done ==='); diff --git a/matlabfiles/quadcopter_model.p b/matlabfiles/quadcopter_model.p new file mode 100644 index 0000000..a75ed96 Binary files /dev/null and b/matlabfiles/quadcopter_model.p differ diff --git a/matlabfiles/rk4_step.m b/matlabfiles/rk4_step.m new file mode 100644 index 0000000..d0042c8 --- /dev/null +++ b/matlabfiles/rk4_step.m @@ -0,0 +1,22 @@ +function [x_next, dxdt_avg] = rk4_step(x, u, dt) +%RK4_STEP Perform a single RK4 integration step using quadcopter_model. +% x: 12x1 state vector +% u: 4x1 control input (held constant over the step) +% dt: time step +% Returns: +% x_next: 12x1 next state +% dxdt_avg: 12x1 RK4 averaged derivative + +% Ensure column vectors +x = x(:); +u = u(:); + +k1 = quadcopter_model(x, u); +k2 = quadcopter_model(x + 0.5*dt*k1, u); +k3 = quadcopter_model(x + 0.5*dt*k2, u); +k4 = quadcopter_model(x + dt*k3, u); + +dxdt_avg = (k1 + 2*k2 + 2*k3 + k4) / 6.0; +x_next = x + dt * dxdt_avg; + +end diff --git a/matlabfiles/simulate_rk4.m b/matlabfiles/simulate_rk4.m new file mode 100644 index 0000000..c880a1d --- /dev/null +++ b/matlabfiles/simulate_rk4.m @@ -0,0 +1,51 @@ +function [X, DX] = simulate_rk4(x0, U, dt, tau) +%SIMULATE_RK4 Run N-step simulation with input LPF and RK4 inside MATLAB. +% x0: 12x1 initial state +% U: Tx4 control inputs (unfiltered); if Tx1, it's broadcast to 4 motors +% dt: time step (scalar) +% tau: input LPF time constant (seconds) +% Returns: +% X: (T+1)x12 state trace (rows are timesteps) +% DX: T x12 RK4-averaged derivatives per step + +% Normalize shapes and types +x = x0(:); +T = size(U, 1); +if size(U,2) == 1 + U = repmat(U, 1, 4); +end + +% Preallocate using like-types +X = zeros(T+1, numel(x), 'like', x); +DX = zeros(T, numel(x), 'like', x); +X(1,:) = x(:).'; + +% LPF state and alpha +% Allocate filter state as numeric like x to avoid class issues +filt = zeros(1, size(U,2), 'like', x); +if tau <= 0 + alpha = 1.0; +else + alpha = dt / (tau + dt); +end + +for t = 1:T + % Input filtering and saturation + filt = filt + alpha * (U(t,:) - filt); + voltage = 10 * tanh(filt); + ucol = voltage(:); + + % RK4 with constant input over the step + k1 = quadcopter_model(x, ucol); + k2 = quadcopter_model(x + 0.5*dt*k1, ucol); + k3 = quadcopter_model(x + 0.5*dt*k2, ucol); + k4 = quadcopter_model(x + dt*k3, ucol); + + dxdt_avg = (k1 + 2*k2 + 2*k3 + k4) / 6.0; + x = x + dt * dxdt_avg; + + X(t+1,:) = x(:).'; + DX(t,:) = dxdt_avg(:).'; +end + +end diff --git a/matlabfiles/simulate_rk4_final.m b/matlabfiles/simulate_rk4_final.m new file mode 100644 index 0000000..5f250fc --- /dev/null +++ b/matlabfiles/simulate_rk4_final.m @@ -0,0 +1,43 @@ +function xN = simulate_rk4_final(x0, U, dt, tau) +%SIMULATE_RK4_FINAL Run N-step simulation and return only final state. +% x0: 12x1 initial state +% U: Tx4 control inputs (unfiltered); if Tx1, it's broadcast to 4 motors +% dt: time step (scalar) +% tau: input LPF time constant (seconds) +% Returns: +% xN: 12x1 final state after T steps + +% Normalize shapes and types +x = x0(:); +T = size(U, 1); +if size(U,2) == 1 + U = repmat(U, 1, 4); +end + +% LPF state and alpha +filt = zeros(1, size(U,2), 'like', x); +if tau <= 0 + alpha = 1.0; +else + alpha = dt / (tau + dt); +end + +for t = 1:T + % Input filtering and saturation + filt = filt + alpha * (U(t,:) - filt); + voltage = 10 * tanh(filt); + ucol = voltage(:); + + % RK4 with constant input over the step + k1 = quadcopter_model(x, ucol); + k2 = quadcopter_model(x + 0.5*dt*k1, ucol); + k3 = quadcopter_model(x + 0.5*dt*k2, ucol); + k4 = quadcopter_model(x + dt*k3, ucol); + + dxdt_avg = (k1 + 2*k2 + 2*k3 + k4) / 6.0; + x = x + dt * dxdt_avg; +end + +xN = x; + +end diff --git a/optimization_results/u_opt_gru.pt b/optimization_results/u_opt_gru.pt new file mode 100644 index 0000000..aafcc84 Binary files /dev/null and b/optimization_results/u_opt_gru.pt differ diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..3c40c02 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,33 @@ +[project] +name = "dronecontrol" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.11.0,<3.12.0" +dependencies = [ + "cvxpy>=1.7.3", + "filterpy>=1.4.5", + "ipykernel>=7.1.0", + "lightning>=2.5.5", + "matlab>=0.1", + "matplotlib>=3.10.7", + "numpy>=2.3.4", + "pandas>=2.3.3", + "pytest>=8.4.2", + "reportlab>=4.4.4", + "scikit-learn>=1.5.0", + "torch>=2.9.0", +] + +[build-system] +requires = ["setuptools>=61.0", "wheel"] +build-backend = "setuptools.build_meta" + + +[tool.setuptools] +include-package-data = true + +[tool.setuptools.packages.find] +where = ["./src"] +include = ["dronecontrol"] +exclude = ["tests*", "*.tests*", "*.tests"] diff --git a/results/position_over_time.png b/results/position_over_time.png new file mode 100644 index 0000000..4ceca66 Binary files /dev/null and b/results/position_over_time.png differ diff --git a/run_control_demo.py b/run_control_demo.py new file mode 100644 index 0000000..fcecc2e --- /dev/null +++ b/run_control_demo.py @@ -0,0 +1,73 @@ +"""Demo runner that exercises ControlRunner with a simple AccelModel. + +Usage: run with the project's venv Python. +""" +from pathlib import Path +import sys +# ensure `src` is importable +sys.path.insert(0, str(Path(__file__).resolve().parent / "src")) + +import torch +import torch.nn as nn +from dronecontrol.commande.control_runner import ControlRunner + + +class AccelModel(nn.Module): + """Simple accel model: returns u - g so optimizer must pick u ~= g to hover.""" + + def __init__(self, g: float = 9.81): + super().__init__() + self.g = g + + def forward(self, u: torch.Tensor) -> torch.Tensor: + # assume u has shape (horizon, 1) + return u - self.g + + +def main(): + device = "cpu" + g = 9.81 + dt = 0.1 + horizon = 20 + x0 = 1.0 + v0 = 0.0 + + x_ref = torch.full((horizon, 1), x0, dtype=torch.float32) + + # simple Q and R + Q = torch.eye(horizon) + R = torch.eye(horizon) + + runner = ControlRunner( + ckpt_path=None, + model_class=AccelModel, + model_kwargs={"g": g}, + device=device, + ) + + u_opt = runner.run( + x_ref=x_ref, + x0=x0, + v0=v0, + horizon=horizon, + dt=dt, + Q=Q, + R=R, + lr=1.0, + max_iter=40, + history_size=10, + max_epochs=80, + ) + + u_np = u_opt.detach().cpu().numpy().flatten() + print(f"u_opt mean={u_np.mean():.4f}, std={u_np.std():.4f}") + print("u_opt (first 10):", u_np[:10]) + + # save result + out_path = Path("predictions_plots") + out_path.mkdir(exist_ok=True) + torch.save(u_opt, out_path / "u_opt.pt") + + +if __name__ == "__main__": + main() diff --git a/run_model.py b/run_model.py new file mode 100644 index 0000000..a3eac7d --- /dev/null +++ b/run_model.py @@ -0,0 +1,125 @@ +"""Complete training pipeline with Lightning.""" + +import pytorch_lightning as pl +from pytorch_lightning.loggers import CSVLogger +from pytorch_lightning.callbacks import ModelCheckpoint, EarlyStopping + +from dronecontrol.data_process.data_cleaning import AVDataCleaner +from dronecontrol.data_process.data_loader import AVDataLoader +from dronecontrol.models.gru_module import GRU +from dronecontrol.models.lstm import LSTM +from dronecontrol.models.rnn import model_nn +from dronecontrol.globals import INPUT_DATA, OUTPUT_DATA, BASEDIR + + +def main(): + """Main training pipeline.""" + + # Configuration + seed = 42 + epochs = 100 + batch_size = 32 + lr = 1e-2 + val_split = 0.2 + test_split = 0.1 + hidden_dim = 32 + num_layers = 2 + dropout = 0.0 + log_dir = "logs" + checkpoint_dir = "models_checkpoints" + # Set seed for reproducibility + pl.seed_everything(seed) + + print("=" * 60) + print("DRONE CONTROL MODEL TRAINING PIPELINE") + print("=" * 60) + + # ==================== DATA LOADING ==================== + print("\n[1/4] Loading data...") + cleaner = AVDataCleaner(str(INPUT_DATA), str(OUTPUT_DATA)) + + # ==================== DATA CLEANING ==================== + print("[2/4] Cleaning data...") + input_data, output_data = cleaner.get_clean_data() + print(f" ✓ Data cleaned: {input_data.shape[0]} samples remaining") + print(f" Input shape: {input_data.shape}") + print(f" Output shape: {output_data.shape}") + + # ==================== DATA MODULE ==================== + print("[3/4] Setting up Lightning DataModule...") + data_module = AVDataLoader( + input=input_data, + target=output_data, + batch_size=batch_size, + val_split=val_split, + test_split=test_split + ) + + # Get data dimensions + + print(f" ✓ DataModule configured") + print(f" Batch size: {batch_size}") + # ==================== MODEL ==================== + print("[4/4] Initializing GRU model...") + + model = GRU( + input_dim=1, + output_dim=1, + hidden_dim=hidden_dim, + num_layers=num_layers, + dropout=dropout, + lr=lr, + ) + + print(f" ✓ GRU model initialized") + print(f" Model parameters: {sum(p.numel() for p in model.parameters()):,}") + + # ==================== LOGGER & CALLBACKS ==================== + logger = CSVLogger( + save_dir=BASEDIR / log_dir, + name="accel_vs_voltage" + ) + + checkpoint_callback = ModelCheckpoint( + dirpath=BASEDIR / checkpoint_dir / "accel_vs_voltage", + filename="gru-{epoch:02d}-{val_loss:.4f}", + monitor="val_loss", + mode="min", + save_top_k=1 + ) + + early_stopping = EarlyStopping( + monitor="val_loss", + patience=10, + mode="min", + verbose=True + ) + + # ==================== TRAINER ==================== + print("\n" + "=" * 60) + print("STARTING TRAINING") + print("=" * 60 + "\n") + + trainer = pl.Trainer( + max_epochs=epochs, + logger=logger, + callbacks=[checkpoint_callback, early_stopping], + accelerator="auto", + devices="auto", + log_every_n_steps=10, + enable_progress_bar=True + ) + + # Train + trainer.fit(model, data_module) + trainer.test(model, data_module) + + print("\n" + "=" * 60) + print("TRAINING COMPLETED") + print("=" * 60) + print(f" ✓ Best model saved to: {checkpoint_callback.dirpath}") + print(f" ✓ Logs saved to: {logger.log_dir}") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/run_optim.py b/run_optim.py new file mode 100644 index 0000000..e54f9ea --- /dev/null +++ b/run_optim.py @@ -0,0 +1,103 @@ +"""Run the optimizer with a GRU model for drone control.""" + +import torch +import pytorch_lightning as pl +from pathlib import Path +import numpy as np + + +from dronecontrol.models.gru_module import GRU +from dronecontrol.commande.MPC_torch import MPCTorch +from dronecontrol.commande.MPC_cvxpy import MPCCVXPY + + +def main(): + """Run optimization with GRU model.""" + + # Configuration + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + + g = 9.81 + dt = 0.1 + horizon = 30 + nb_steps = 300 # Number of optimization steps + hidden_dim = 8 + num_layers = 1 + dropout = 0.0 + + # Load trained GRU model (assuming checkpoint exists) + # For now, create untrained model - in practice, load from checkpoint + # gru_model = GRU( + # input_dim=1, + # output_dim=1, + # hidden_dim=hidden_dim, + # num_layers=num_layers, + # dropout=dropout, + # lr=1e-2 + # ) + + # Note: In practice, load checkpoint like: + # checkpoint_path = "path/to/gru-checkpoint.ckpt" + gru_model = GRU.load_from_checkpoint(r"models/accel_vs_voltage/gru/epoch=120-val_loss=0.0313.ckpt") + gru_model.to(device) + + # Set model to evaluation mode + # Initialize optimizer + + optimizer = MPCTorch( + accel_model=gru_model, + dt=dt, + Q=np.eye(horizon) * 10, # Control effort weight + R=np.eye(horizon) * 1.0, # Tracking error weight + S=np.eye(horizon) * 0.1, # Terminal state weight + tau=0.5, # Speed overshoot weight + lr=0.5, + max_epochs=100, + horizon=horizon, + use_ekf=True, # Set to True to use EKF + use_simulink=True, + ) + + + # Initial conditions + x0 = 1.0 # Initial position + v0 = 0.0 # Initial velocity + + # Reference trajectory (hover at initial position) + x_ref = torch.linspace(0, 5, nb_steps//2).tolist() + torch.linspace(5, 5, nb_steps//2).tolist() + x_ref = torch.tensor(x_ref, dtype=torch.float32) + + + print("=" * 60) + print("DRONE CONTROL OPTIMIZATION WITH GRU MODEL") + print("=" * 60) + print(f"Initial position: {x0}") + print(f"Initial velocity: {v0}") + print(f"Horizon: {horizon}") + print(f"Number of steps: {nb_steps}") + print(f"Using EKF: {optimizer.use_ekf}") + + # Run optimization + print("\nStarting optimization...") + u_opt, histories = optimizer.solve( + x_ref=x_ref, + x0=x0, + v0=v0, + verbose=True + ) + + print("\nOptimization completed!") + print(f"Optimized control trajectory shape: {u_opt.shape}") + print(f"Mean control: {u_opt.mean().item():.4f}") + print(f"Std control: {u_opt.std().item():.4f}") + + # Save results + results_dir = Path("optimization_results") + results_dir.mkdir(exist_ok=True) + torch.save(u_opt, results_dir / "u_opt_gru.pt") + + print(f"\nResults saved to: {results_dir / 'u_opt_gru.pt'}") + + +if __name__ == "__main__": + main() diff --git a/run_optim_profile.py b/run_optim_profile.py new file mode 100644 index 0000000..c32715c --- /dev/null +++ b/run_optim_profile.py @@ -0,0 +1,114 @@ +"""Profiler for `run_optim_report.py` that captures both CPU and GPU activity. + +This script runs a shortened version of the report with reduced steps/epochs +and collects: + - a Python cProfile binary (`.prof`) for CPU-level profiling, + - a torch.profiler trace (Chrome trace JSON) for CUDA activity, + - a text summary showing top CUDA/CPU hotspots. + +Run with: `python run_optim_profile.py` +""" +import cProfile +import pstats +import io +from pathlib import Path +import torch +from torch.profiler import profile, record_function, ProfilerActivity + +from run_optim_report import ScenarioConfig, load_model, build_mpc, build_reference + + +def run_combined_profile(out_prof: Path, out_trace: Path, out_txt: Path): + # Build config with reduced sizes for quick profiling + cfg = ScenarioConfig() + cfg.nb_steps = 80 + cfg.max_epochs = 8 + cfg.horizon = 20 + cfg.lr = 0.05 + + use_cuda = torch.cuda.is_available() + device = torch.device("cuda" if use_cuda else "cpu") + + # Load model and controller + model = load_model(cfg, device) + controller = build_mpc(model, cfg) + + x0 = 1.0 + v0 = 0.0 + x_ref = build_reference(cfg.nb_steps) + + # 1) CPU profiling (cProfile) for Python-level hotspots + pr = cProfile.Profile() + pr.enable() + + # 2) Torch profiler for CUDA activity (if available) + activities = [ProfilerActivity.CPU] + if use_cuda: + activities.append(ProfilerActivity.CUDA) + + # Warm-up a small controller run to avoid first-call overhead + _ = controller.solve(x_ref=x_ref, x0=x0, v0=v0, verbose=False) + + if use_cuda: + torch.cuda.synchronize() + + with profile( + activities=activities, + record_shapes=False, + profile_memory=True, + with_stack=True, + ) as prof: + with record_function("run_mpc_solve"): + # Full short run (the main thing we want to inspect) + u_hist, x_hist, v_hist, a_hist = controller.solve( + x_ref=x_ref, + x0=x0, + v0=v0, + verbose=False, + ) + + if use_cuda: + torch.cuda.synchronize() + + pr.disable() + + # Save cProfile binary for Python-level inspection + out_prof.parent.mkdir(parents=True, exist_ok=True) + pr.dump_stats(str(out_prof)) + + # Save torch profiler chrome trace for viewing in Chrome/TensorBoard + out_trace.parent.mkdir(parents=True, exist_ok=True) + try: + prof.export_chrome_trace(str(out_trace)) + except Exception: + # Older torch versions may not implement export_chrome_trace + pass + + # Prepare text summary combining cProfile and torch profiler key_averages + s = io.StringIO() + s.write("=== cProfile (top 30 by cumulative time) ===\n") + ps = pstats.Stats(pr, stream=s) + ps.strip_dirs().sort_stats("cumtime").print_stats(30) + + s.write("\n=== torch.profiler key_averages (top 50 by self_cuda_time_total) ===\n") + try: + ka = prof.key_averages() + # CUDA-heavy summary + s.write(ka.table(sort_by="self_cuda_time_total", row_limit=50)) + s.write("\n\n") + s.write(ka.table(sort_by="self_cpu_time_total", row_limit=50)) + except Exception: + s.write("(torch.profiler summary not available)\n") + + out_txt.write_text(s.getvalue(), encoding="utf-8") + + print(f"cProfile saved to: {out_prof}") + print(f"Trace (chrome) saved to: {out_trace}") + print(f"Combined summary written to: {out_txt}") + + +if __name__ == "__main__": + out_prof = Path("run_optim_profile.prof") + out_trace = Path("run_optim_torch_trace.json") + out_txt = Path("run_optim_profile_summary.txt") + run_combined_profile(out_prof, out_trace, out_txt) diff --git a/run_optim_profile_summary.txt b/run_optim_profile_summary.txt new file mode 100644 index 0000000..69fd6d9 --- /dev/null +++ b/run_optim_profile_summary.txt @@ -0,0 +1,155 @@ +=== cProfile (top 30 by cumulative time) === + 383004 function calls (368922 primitive calls) in 10.638 seconds + + Ordered by: cumulative time + List reduced from 382 to 30 due to restriction <30> + + ncalls tottime percall cumtime percall filename:lineno(function) + 1 0.000 0.000 7.964 7.964 profiler.py:805(__enter__) + 1 0.000 0.000 7.964 7.964 profiler.py:815(start) + 1 0.000 0.000 7.964 7.964 profiler.py:868(_transit_action) + 1 0.000 0.000 7.926 7.926 profiler.py:205(start_trace) + 1 0.000 0.000 7.926 7.926 profiler.py:370(_start_trace) + 1 7.926 7.926 7.926 7.926 {built-in method torch._C._autograd._enable_profiler} + 1 0.006 0.006 2.674 2.674 base_mpc.py:192(solve) + 80 0.004 0.000 2.335 0.029 base_mpc.py:307(step) + 80 0.089 0.001 2.294 0.029 MPC_torch.py:73(optimize_control) + 640 0.146 0.000 1.231 0.002 base_mpc.py:74(_compute_trajectory_wrt_NN) +3120/1040 0.004 0.000 1.111 0.001 module.py:1771(_wrapped_call_impl) +3120/1040 0.007 0.000 1.109 0.001 module.py:1779(_call_impl) + 1040 0.006 0.000 1.105 0.001 gru_module.py:54(forward) + 1040 0.010 0.000 0.871 0.001 rnn.py:1333(forward) + 1040 0.832 0.001 0.832 0.001 {built-in method torch.gru} + 1360 0.003 0.000 0.817 0.001 graph.py:832(_engine_run_backward) + 1360 0.813 0.001 0.813 0.001 {method 'run_backward' of 'torch._C._EngineBase' objects} + 640 0.003 0.000 0.699 0.001 _tensor.py:570(backward) + 640 0.005 0.000 0.695 0.001 __init__.py:243(backward) + 80 0.006 0.000 0.325 0.004 neural_ekf.py:89(step) + 160 0.011 0.000 0.237 0.001 functional.py:574(jacobian) + 1040 0.002 0.000 0.217 0.000 linear.py:130(forward) + 1040 0.214 0.000 0.214 0.000 {built-in method torch._C._nn.linear} + 80 0.003 0.000 0.200 0.002 neural_ekf.py:73(compute_F) + 640 0.007 0.000 0.182 0.000 optimizer.py:497(wrapper) + 720 0.005 0.000 0.177 0.000 functional.py:167(_autograd_grad) + 720 0.004 0.000 0.172 0.000 __init__.py:365(grad) + 640 0.004 0.000 0.139 0.000 optimizer.py:62(_use_grad) + 640 0.004 0.000 0.133 0.000 adam.py:213(step) + 640 0.001 0.000 0.110 0.000 optimizer.py:133(maybe_fallback) + + + +=== torch.profiler key_averages (top 50 by self_cuda_time_total) === +------------------------------------------------------- ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ + Name Self CPU % Self CPU CPU total % CPU total CPU time avg Self CUDA Self CUDA % CUDA total CUDA time avg CPU Mem Self CPU Mem CUDA Mem Self CUDA Mem # of Calls +------------------------------------------------------- ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ + run_mpc_solve 0.00% 0.000us 0.00% 0.000us 0.000us 5.911s 2884.24% 5.911s 2.955s 0 B 0 B 0 B 0 B 2 + Optimizer.step#Adam.step 0.00% 0.000us 0.00% 0.000us 0.000us 101.554ms 49.55% 101.554ms 158.678us 0 B 0 B 0 B 0 B 640 + aten::_cudnn_rnn_backward 5.57% 236.142ms 9.64% 408.699ms 300.514us 37.013ms 18.06% 41.264ms 30.341us 0 B 0 B 2.27 MB -15.64 GB 1360 + aten::copy_ 1.38% 58.391ms 4.25% 180.006ms 16.422us 20.633ms 10.07% 21.187ms 1.933us 0 B -1.25 KB 0 B 0 B 10961 +void at::native::vectorized_elementwise_kernel<4, at... 0.00% 0.000us 0.00% 0.000us 0.000us 19.572ms 9.55% 19.572ms 2.355us 0 B 0 B 0 B 0 B 8312 + aten::mul 1.83% 77.496ms 3.10% 131.609ms 17.137us 19.356ms 9.44% 19.359ms 2.521us 0 B 0 B 3.72 MB 3.72 MB 7680 + Memcpy DtoD (Device -> Device) 0.00% 0.000us 0.00% 0.000us 0.000us 16.835ms 8.21% 16.835ms 2.206us 0 B 0 B 0 B 0 B 7632 + aten::_cudnn_rnn 1.52% 64.509ms 3.00% 127.190ms 122.298us 16.410ms 8.01% 16.410ms 15.779us 0 B 0 B 3.71 MB -8.13 GB 1040 +void at::native::vectorized_elementwise_kernel<4, at... 0.00% 0.000us 0.00% 0.000us 0.000us 15.802ms 7.71% 15.802ms 2.101us 0 B 0 B 0 B 0 B 7520 + aten::fill_ 0.96% 40.673ms 2.25% 95.308ms 12.647us 15.800ms 7.71% 15.800ms 2.097us 0 B 0 B 0 B 0 B 7536 +void RNN_blockPersist_bp_GRU_FMA::type internal::gemvx... 0.00% 0.000us 0.00% 0.000us 0.000us 9.306ms 4.54% 9.306ms 3.681us 0 B 0 B 0 B 0 B 2528 + aten::addmv_ 0.89% 37.835ms 1.31% 55.704ms 21.759us 8.978ms 4.38% 8.978ms 3.507us 0 B 0 B 0 B 0 B 2560 +void gemvNSP_kernel Device) 0.00% 0.000us 0.00% 0.000us 0.000us 3.869ms 1.89% 3.869ms 1.727us 0 B 0 B 0 B 0 B 2240 +void reduce_1Block_kernel(cudnnRNNMode_t... 0.00% 0.000us 0.00% 0.000us 0.000us 2.670ms 1.30% 2.670ms 2.567us 0 B 0 B 0 B 0 B 1040 + aten::_foreach_div_ 0.10% 4.361ms 0.20% 8.325ms 13.007us 2.339ms 1.14% 2.339ms 3.655us 0 B 0 B 0 B 0 B 640 +void at::native::(anonymous namespace)::multi_tensor... 0.00% 0.000us 0.00% 0.000us 0.000us 2.339ms 1.14% 2.339ms 3.655us 0 B 0 B 0 B 0 B 640 +void gemmk1_kernel GRU: + model = GRU.load_from_checkpoint(cfg.checkpoint_path) + model.to(device) + return model + + +def build_mpc(model: GRU, cfg: ScenarioConfig) -> MPCTorch: + # Create decaying weights: current control is more important than future + decay_weights = np.exp(-np.arange(cfg.horizon, dtype=np.float32) * 0.1) + Q = np.diag(decay_weights * cfg.control_weight) + R = np.diag(decay_weights * cfg.tracking_weight) + S = np.eye(cfg.horizon, dtype=np.float32) * (cfg.tracking_weight * 0.1) + + return MPCTorch( + accel_model=model, + dt=cfg.dt, + horizon=cfg.horizon, + Q=Q, + R=R, + S=S, + tau=cfg.tau, + lr=cfg.lr, + max_epochs=cfg.max_epochs, + use_ekf=cfg.use_ekf, + use_simulink=cfg.use_simulink, + optimize_trajectory=cfg.optimize_trajectory, + ) + + + +def integrate_acceleration( + acceleration: torch.Tensor, + dt: float, + x0: float, + v0: float, +) -> tuple[np.ndarray, np.ndarray]: + if acceleration.numel() == 0: + return np.array([], dtype=np.float32), np.array([], dtype=np.float32) + + accel_cpu = acceleration.detach().cpu() + dt_tensor = torch.tensor(dt, dtype=accel_cpu.dtype) + v0_tensor = torch.tensor(v0, dtype=accel_cpu.dtype) + x0_tensor = torch.tensor(x0, dtype=accel_cpu.dtype) + + vel = v0_tensor + torch.cumsum(accel_cpu * dt_tensor, dim=0) + pos = x0_tensor + torch.cumsum(vel * dt_tensor, dim=0) + + return pos.numpy(), vel.numpy() + + +def plot_results( + time_grid: np.ndarray, + x_ref: np.ndarray, + optimized_trajectory: np.ndarray, + x_sim: np.ndarray, + x_nn: np.ndarray, + v_sim: np.ndarray, + v_nn: np.ndarray, + a_sim: np.ndarray, + a_nn: np.ndarray, + u_history: np.ndarray, + figure_path: Path, +) -> None: + plt.style.use("seaborn-v0_8-darkgrid") + # Create 5 stacked plots: position, tracking error, velocity, acceleration, control + fig, axes = plt.subplots(5, 1, figsize=(12, 14), sharex=True) + + # Position tracking: compare reference, simulink and NN predictions + axes[0].plot(time_grid, optimized_trajectory, label="Filtered reference", color="tab:purple", linewidth=2, alpha=0.7) + axes[0].plot(time_grid, x_ref, label="Reference", color="tab:blue", linewidth=2, linestyle=":") + axes[0].plot(time_grid, x_sim, label="Position (Simulink)", color="tab:orange", linewidth=2) + axes[0].plot(time_grid, x_nn, label="Position (NN)", color="tab:green", linestyle="--") + axes[0].set_ylabel("Position [m]") + axes[0].set_title("Position tracking: Simulink vs NN") + axes[0].legend(loc="upper left", fontsize=8, framealpha=0.6) + + + # Control effort + axes[1].plot(time_grid, u_history, color="tab:blue") + axes[1].set_ylabel("Control input") + axes[1].set_title("Control sequence") + axes[1].set_xlabel("Time [s]") + + # Tracking error for both Simulink and NN + tracking_error_sim = x_sim - x_ref + tracking_error_nn = x_nn - x_ref + axes[2].plot(time_grid, tracking_error_sim, label="Error (Sim)", color="tab:red") + axes[2].plot(time_grid, tracking_error_nn, label="Error (NN)", color="tab:pink", linestyle="--") + axes[2].fill_between(time_grid, tracking_error_sim, 0, color="tab:red", alpha=0.15) + axes[2].set_title("Tracking error") + axes[2].set_ylabel("Error [m]") + axes[2].legend(loc="upper left", fontsize=8, framealpha=0.6) + + # Acceleration: Simulink measured vs NN predicted + axes[3].plot(time_grid, a_sim, label="Accel (Simulink)", color="tab:brown") + axes[3].plot(time_grid, a_nn, label="Accel (NN)", color="tab:cyan", linestyle="--") + axes[3].set_ylabel("Acceleration [m/s^2]") + axes[3].set_title("Acceleration: Simulink vs NN") + axes[3].legend(loc="upper left", fontsize=8, framealpha=0.6) + + # Velocity comparison + axes[4].plot(time_grid, v_sim, label="Velocity (Simulink)", color="tab:purple", linewidth=2) + axes[4].plot(time_grid, v_nn, label="Velocity (NN)", color="tab:olive", linestyle="--") + axes[4].set_ylabel("Velocity [m/s]") + axes[4].set_title("Velocity estimates") + axes[4].legend(loc="upper left", fontsize=8, framealpha=0.6) + + + for axis in axes: + axis.grid(True, linestyle=":", linewidth=0.5) + + fig.suptitle("GRU-based MPC report", fontsize=16) + fig.tight_layout(rect=(0.0, 0.03, 1.0, 0.97)) + + figure_path.parent.mkdir(parents=True, exist_ok=True) + fig.savefig(figure_path, dpi=300, bbox_inches="tight") + plt.close(fig) + + +def main() -> None: + cfg = ScenarioConfig() + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + + print("=" * 60) + print("DRONE CONTROL OPTIMIZATION REPORT") + print("=" * 60) + print(f"Device: {device}") + print(f"Horizon: {cfg.horizon}") + + model = load_model(cfg, device) + controller = build_mpc(model, cfg) + + x0 = 0.0 + v0 = 0.0 + + x_ref = torch.tensor([0.5, -0.25, 0], dtype=torch.float32) + + print("Running MPC optimization...") + u_hist, histories = controller.solve( + x_ref=x_ref, + x0=x0, + v0=v0, + verbose=True, + ) + + # Extract histories for simulink and NN + x_sim = histories["simulink"]["x"] + v_sim = histories["simulink"]["v"] + a_sim = histories["simulink"]["a"] + + x_nn = histories["nn"]["x"] + v_nn = histories["nn"]["v"] + a_nn = histories["nn"]["a"] + + x_ref = histories["references"]["steps"] + optimized_trajectory = histories["references"]["filtered"] + + time_grid = np.arange(optimized_trajectory.shape[0], dtype=np.float32) * cfg.dt + + x_sim_np = x_sim.detach().cpu().numpy() + v_sim_np = v_sim.detach().cpu().numpy() + a_sim_np = a_sim.detach().cpu().numpy() + + x_nn_np = x_nn.detach().cpu().numpy() + v_nn_np = v_nn.detach().cpu().numpy() + a_nn_np = a_nn.detach().cpu().numpy() + + u_hist_np = u_hist.detach().cpu().numpy() + x_ref_np = x_ref.detach().cpu().numpy() + optimized_trajectory_np = optimized_trajectory.detach().cpu().numpy() + + # --- Save plotted data as CSVs and a combined DataFrame --- + report_dir = cfg.report_dir + report_dir.mkdir(parents=True, exist_ok=True) + + # Prepare data dictionary (ensure same length by trimming to min length) + data_dict = { + "time": time_grid, + "optimized_trajectory": optimized_trajectory_np, + "reference": x_ref_np, + "x_sim": x_sim_np, + "x_nn": x_nn_np, + "v_sim": v_sim_np, + "v_nn": v_nn_np, + "a_sim": a_sim_np, + "a_nn": a_nn_np, + "u_history": u_hist_np, + } + + # Trim all arrays to the shortest length to avoid shape mismatch + min_len = min(len(v) for v in data_dict.values()) + trimmed = {k: np.asarray(v)[:min_len] for k, v in data_dict.items()} + + df = pd.DataFrame(trimmed) + combined_path = report_dir / "best_model" / "gru_mpc_report_0.002_cons.csv" + df.to_csv(combined_path, index=False) + + # Only save the combined DataFrame as a single CSV (contains all series) + figure_path = report_dir / "best_model" / "gru_mpc_report_0.002_cons.png" + + plot_results( + time_grid=time_grid, + x_ref=x_ref_np, + optimized_trajectory=optimized_trajectory_np, + x_sim=x_sim_np, + x_nn=x_nn_np, + v_sim=v_sim_np, + v_nn=v_nn_np, + a_sim=a_sim_np, + a_nn=a_nn_np, + u_history=u_hist_np, + figure_path=figure_path, + ) + + + + print("Optimization completed.") + print(f"Figure saved to {figure_path}") + + +if __name__ == "__main__": + main() diff --git a/src/data_augmentation.py b/src/data_augmentation.py deleted file mode 100644 index 1f83ff3..0000000 --- a/src/data_augmentation.py +++ /dev/null @@ -1,68 +0,0 @@ -"""Data augmentation helpers for UAV control pipeline.""" - -from __future__ import annotations - -import logging -from typing import Dict, Optional, Tuple - -import numpy as np -import pandas as pd - -LOGGER = logging.getLogger(__name__) - - -def add_gaussian_noise( - inputs: np.ndarray, - outputs: np.ndarray, - noise_std: float, - seed: Optional[int] = None, -) -> Tuple[np.ndarray, np.ndarray]: - if noise_std <= 0: - return inputs, outputs - rng = np.random.default_rng(seed) - noisy_inputs = inputs + rng.normal(scale=noise_std, size=inputs.shape) - noisy_outputs = outputs + rng.normal(scale=noise_std, size=outputs.shape) - augmented_inputs = np.concatenate([inputs, noisy_inputs], axis=0) - augmented_outputs = np.concatenate([outputs, noisy_outputs], axis=0) - LOGGER.info("Augmented data with Gaussian noise std=%s", noise_std) - return augmented_inputs, augmented_outputs - - -def slice_windows( - inputs: np.ndarray, - outputs: np.ndarray, - window_size: int, - stride: int, -) -> Dict[str, np.ndarray]: - if window_size <= 0: - raise ValueError("window_size must be positive") - if stride <= 0: - raise ValueError("stride must be positive") - - sequences_x = [] - sequences_y = [] - for start in range(0, len(inputs) - window_size + 1, stride): - end = start + window_size - sequences_x.append(inputs[start:end]) - sequences_y.append(outputs[end - 1]) - if not sequences_x: - raise ValueError("Insufficient samples to create any sliding windows") - X = np.stack(sequences_x) - Y = np.stack(sequences_y) - LOGGER.info("Created %d sliding windows of size %d", len(X), window_size) - return {"X": X, "Y": Y} - - -def append_simulink_samples( - inputs: np.ndarray, - outputs: np.ndarray, - sim_df: Optional[pd.DataFrame], -) -> Tuple[np.ndarray, np.ndarray]: - if sim_df is None or sim_df.empty: - return inputs, outputs - sim_data = sim_df.select_dtypes(include=[np.number]).to_numpy() - padded_outputs = np.zeros((sim_data.shape[0], outputs.shape[1]), dtype=outputs.dtype) - combined_inputs = np.concatenate([inputs, sim_data], axis=0) - combined_outputs = np.concatenate([outputs, padded_outputs], axis=0) - LOGGER.info("Appended %d Simulink samples", sim_data.shape[0]) - return combined_inputs, combined_outputs diff --git a/src/data_cleaning.py b/src/data_cleaning.py deleted file mode 100644 index 6c6273c..0000000 --- a/src/data_cleaning.py +++ /dev/null @@ -1,68 +0,0 @@ -"""Data cleaning routines for UAV datasets.""" - -from __future__ import annotations - -import logging -from typing import Dict, Tuple - -import numpy as np -import pandas as pd - -LOGGER = logging.getLogger(__name__) - - -def remove_outliers(df: pd.DataFrame, method: str = "iqr", threshold: float = 1.5) -> pd.DataFrame: - numeric_cols = df.select_dtypes(include=[np.number]).columns - if len(numeric_cols) == 0: - return df - mask = pd.Series(True, index=df.index) - if method.lower() == "iqr": - q1 = df[numeric_cols].quantile(0.25) - q3 = df[numeric_cols].quantile(0.75) - iqr = q3 - q1 - lower = q1 - threshold * iqr - upper = q3 + threshold * iqr - mask &= ~((df[numeric_cols] < lower) | (df[numeric_cols] > upper)).any(axis=1) - elif method.lower() == "zscore": - z = (df[numeric_cols] - df[numeric_cols].mean()) / df[numeric_cols].std(ddof=0) - mask &= ~(np.abs(z) > threshold).any(axis=1) - else: - raise ValueError(f"Unsupported outlier removal method: {method}") - filtered = df[mask].reset_index(drop=True) - LOGGER.info("Removed %d outlier rows using %s", len(df) - len(filtered), method) - return filtered - - -def interpolate_missing(df: pd.DataFrame, method: str = "linear") -> pd.DataFrame: - interpolated = df.interpolate(method=method, limit_direction="both") - interpolated = interpolated.ffill().bfill() - missing = interpolated.isna().sum().sum() - if missing: - LOGGER.warning("%d missing values remain after interpolation", missing) - return interpolated - - -def normalize( - df: pd.DataFrame, - scope: str = "per_feature", -) -> Tuple[pd.DataFrame, Dict[str, Dict[str, float]]]: - numeric_cols = df.select_dtypes(include=[np.number]).columns - if scope == "global": - mean = float(df[numeric_cols].values.mean()) - std = float(df[numeric_cols].values.std(ddof=0)) or 1.0 - df_norm = df.copy() - df_norm[numeric_cols] = (df_norm[numeric_cols] - mean) / std - params = {"global": {"mean": mean, "std": std}} - elif scope == "per_feature": - stats = df[numeric_cols].agg(["mean", "std"]) - stats.loc["std", :] = stats.loc["std"].replace(to_replace=0, value=1.0) - df_norm = df.copy() - df_norm[numeric_cols] = (df_norm[numeric_cols] - stats.loc["mean"]) / stats.loc["std"] - params = { - col: {"mean": float(stats.loc["mean", col]), "std": float(stats.loc["std", col])} - for col in numeric_cols - } - else: - raise ValueError(f"Unknown normalization scope: {scope}") - LOGGER.info("Applied %s normalization", scope) - return df_norm, params diff --git a/src/data_loader.py b/src/data_loader.py deleted file mode 100644 index 72075aa..0000000 --- a/src/data_loader.py +++ /dev/null @@ -1,76 +0,0 @@ -"""Data loading and serialization utilities.""" - -from __future__ import annotations - -import logging -from pathlib import Path -from typing import Dict, Tuple - -import numpy as np -import pandas as pd - -LOGGER = logging.getLogger(__name__) - - -def dataframe_to_arrays(inputs: pd.DataFrame, outputs: pd.DataFrame) -> Tuple[np.ndarray, np.ndarray]: - X = inputs.select_dtypes(include=[np.number]).to_numpy(dtype=np.float32) - Y = outputs.select_dtypes(include=[np.number]).to_numpy(dtype=np.float32) - if Y.ndim == 1: - Y = Y[:, None] - return X, Y - - -def train_val_test_split( - X: np.ndarray, - Y: np.ndarray, - val_ratio: float, - test_ratio: float, - seed: int, -) -> Dict[str, np.ndarray]: - if val_ratio + test_ratio >= 1.0: - raise ValueError("Validation and test ratios must sum to less than 1") - rng = np.random.default_rng(seed) - indices = np.arange(len(X)) - rng.shuffle(indices) - X_shuffled = X[indices] - Y_shuffled = Y[indices] - n_total = len(X) - n_test = int(n_total * test_ratio) - n_val = int(n_total * val_ratio) - n_train = n_total - n_val - n_test - split = { - "train": (X_shuffled[:n_train], Y_shuffled[:n_train]), - "val": (X_shuffled[n_train:n_train + n_val], Y_shuffled[n_train:n_train + n_val]), - "test": (X_shuffled[n_train + n_val:], Y_shuffled[n_train + n_val:]), - } - LOGGER.info( - "Split dataset into train=%d, val=%d, test=%d", - len(split["train"][0]), - len(split["val"][0]), - len(split["test"][0]), - ) - return { - "X_train": split["train"][0], - "Y_train": split["train"][1], - "X_val": split["val"][0], - "Y_val": split["val"][1], - "X_test": split["test"][0], - "Y_test": split["test"][1], - } - - -def save_npz(path: str, arrays: Dict[str, np.ndarray]) -> None: - Path(path).parent.mkdir(parents=True, exist_ok=True) - np.savez_compressed(path, **arrays) - LOGGER.info("Persisted dataset to %s", path) - - -def load_npz(path: str) -> Tuple[np.ndarray, ...]: - with np.load(path) as data: - X_train = data["X_train"] - Y_train = data["Y_train"] - X_val = data["X_val"] - Y_val = data["Y_val"] - X_test = data["X_test"] - Y_test = data["Y_test"] - return X_train, Y_train, X_val, Y_val, X_test, Y_test diff --git a/src/dronecontrol/__init__.py b/src/dronecontrol/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/dronecontrol/commande/MPC_cvxpy.py b/src/dronecontrol/commande/MPC_cvxpy.py new file mode 100644 index 0000000..a2aeb52 --- /dev/null +++ b/src/dronecontrol/commande/MPC_cvxpy.py @@ -0,0 +1,282 @@ +import cvxpy as cp +import numpy as np +import torch +from typing import Optional, Tuple + +from dronecontrol.commande.base_mpc import MPC, ArrayLike +from dronecontrol.models.base_module import BaseModel + + +class MPCCVXPY(MPC): + """ + Model Predictive Control using CVXPY for convex optimization. + + Key improvements over PyTorch version: + - Guaranteed convergence for convex problems + - Native constraint handling + - Much faster (specialized QP solvers) + - No gradient graph issues + """ + + def __init__( + self, + accel_model: BaseModel, + dt: float, + horizon: int, + nb_steps: int, + Q: ArrayLike, # Control effort weight matrix + R: ArrayLike, # Tracking error weight matrix + u_min: float = -5.0, + u_max: float = 5.0, + use_ekf: bool = False, + use_simulink: bool = False, + linearization_iters: int = 3 # Number of SQP iterations + ): + """ + Initialize MPC controller. + + Args: + accel_model: Neural network predicting acceleration from control + dt: Time step + horizon: Prediction horizon (number of future steps) + nb_steps: Total number of control steps to execute + Q: Weight matrix on control effort + R: Weight matrix on tracking error + u_min, u_max: Control input bounds + use_ekf: Whether to use EKF for state estimation + use_simulink: Whether to use Simulink for simulation + linearization_iters: Number of SQP iterations for better accuracy + """ + # Call parent constructor + super().__init__( + accel_model=accel_model, + dt=dt, + horizon=horizon, + Q=Q, + R=R, + u_min=u_min, + u_max=u_max, + use_ekf=use_ekf, + use_simulink=use_simulink + ) + + # Store matrices for cost computation + self.Q_matrix = np.array(Q, dtype=np.float32) + self.R_matrix = np.array(R, dtype=np.float32) + self.linearization_iters = linearization_iters + + def linearize_model( + self, + u_nominal: np.ndarray, + hidden: torch.Tensor + ) -> Tuple[np.ndarray, np.ndarray]: + """ + Linearize neural network dynamics around nominal control sequence. + + Approximation: a(u) ≈ a_nom + B·(u - u_nom) + + Returns: + a_nominal: Accelerations at nominal point [horizon] + B: Sensitivity matrix da/du [horizon, horizon] (diagonal) + """ + # Convert to torch + u_torch = torch.tensor(u_nominal, dtype=torch.float32).reshape(1, -1, 1) + h_torch = hidden.detach() if isinstance(hidden, torch.Tensor) else torch.tensor(hidden, dtype=torch.float32) + + # Ensure correct shape [num_layers, 1, hidden_dim] + if h_torch.dim() == 2: + h_torch = h_torch.unsqueeze(1) + + # Nominal trajectory + with torch.no_grad(): + a_nominal_torch, _ = self.accel_model(u_torch, h_torch) + a_nominal = a_nominal_torch.squeeze(0).squeeze(-1).cpu().numpy() + + # Compute sensitivity using finite differences + epsilon = 1e-4 + horizon = len(u_nominal) + B = np.zeros(horizon) + + for i in range(horizon): + u_pert = u_nominal.copy() + u_pert[i] += epsilon + u_pert_torch = torch.tensor(u_pert, dtype=torch.float32).reshape(1, -1, 1) + + with torch.no_grad(): + a_pert_torch, _ = self.accel_model(u_pert_torch, h_torch) + a_pert = a_pert_torch.squeeze(0).squeeze(-1).cpu().numpy() + + B[i] = (a_pert[i] - a_nominal[i]) / epsilon + + return a_nominal, B + + def solve_qp( + self, + x_ref: np.ndarray, + x0: float, + v0: float, + a_nominal: np.ndarray, + B: np.ndarray + ) -> Optional[np.ndarray]: + """ + Solve the quadratic program for MPC. + + Variables: u (control sequence), x (positions), v (velocities) + Objective: min R·||x - x_ref||² + Q·||u||² + Constraints: + - Dynamics: x[t+1] = x[t] + v[t]·dt + 0.5·a[t]·dt² + - Control bounds: u_min ≤ u ≤ u_max + + Returns: + u_optimal: Optimal control sequence [horizon], or None if infeasible + """ + horizon = len(x_ref) + # Define optimization variables + u = cp.Variable(horizon) + x = cp.Variable(horizon) + v = cp.Variable(horizon) + + # Constraints + constraints = [] + + # Initial conditions + constraints.append(x[0] == x0) + constraints.append(v[0] == v0) + + # System dynamics with linearized model + for t in range(horizon - 1): + # Linearized acceleration: a[t] = a_nominal[t] + B[t]·u[t] + a_t = a_nominal[t] + B[t] * u[t] + + # Euler integration + constraints.append(v[t+1] == v[t] + a_t * self.dt) + constraints.append( + x[t+1] == x[t] + v[t] * self.dt + 0.5 * a_t * self.dt**2 + ) + + # Control bounds + constraints.append(u >= self.u_min) + constraints.append(u <= self.u_max) + + # Objective function using matrix quadratic forms + # ||x - x_ref||_R^2 = (x - x_ref)^T R (x - x_ref) + error = x - x_ref + R_mat = self.R_matrix[:horizon, :horizon] + tracking_cost = cp.quad_form(error, R_mat) + + # ||u||_Q^2 = u^T Q u + Q_mat = self.Q_matrix[:horizon, :horizon] + control_cost = cp.quad_form(u, Q_mat) + + objective = cp.Minimize(tracking_cost + control_cost) + + # Solve + problem = cp.Problem(objective, constraints) + + try: + problem.solve(solver=cp.OSQP, verbose=False, eps_abs=1e-5, eps_rel=1e-5) + + if problem.status in [cp.OPTIMAL, cp.OPTIMAL_INACCURATE]: + return u.value + else: + print(f"⚠️ Solver status: {problem.status}") + return None + + except Exception as e: + print(f"Solver error: {e}") + return None + + def optimize_control( + self, + x_ref: torch.Tensor, + u: torch.Tensor, + hk: torch.Tensor, + xk: float, + vk: float, + verbose: bool = False + ) -> float: + """ + Optimize control sequence over the horizon using CVXPY with Sequential Quadratic Programming. + + Args: + x_ref: Reference trajectory [horizon] (torch.Tensor) + u: Initial control sequence [1, horizon, 1] (not used, CVXPY initializes internally) + hk: Initial hidden state [num_layers, 1, hidden_dim] + xk: Initial position + vk: Initial velocity + verbose: Whether to print optimization progress + + Returns: + u_optimal: Optimized control for first step + """ + # Convert torch tensors to numpy + x_ref_np = x_ref.detach().cpu().numpy() + horizon = len(x_ref_np) + + # Initialize with zero control + u_current = np.zeros(horizon) + + # Sequential Quadratic Programming (SQP-like approach) + for iteration in range(self.linearization_iters): + # 1. Linearize around current control + a_nominal, B = self.linearize_model(u_current, hk) + + # 2. Solve QP + u_new = self.solve_qp(x_ref_np, xk, vk, a_nominal, B) + + if u_new is None: + print(f"⚠️ Optimization failed at iteration {iteration}") + break + + # 3. Update + u_current = u_new + + # Log first iteration, last iteration, or all if verbose + should_log = (iteration == 0 or iteration == self.linearization_iters - 1 or verbose) + + if should_log: + # Compute cost for debugging + u_torch = torch.tensor(u_current, dtype=torch.float32).reshape(1, -1, 1) + with torch.no_grad(): + a_torch, _ = self.accel_model(u_torch, hk) + a = a_torch.squeeze().numpy() + + # Simulate trajectory + x_pred = np.zeros(horizon) + v_pred = np.zeros(horizon) + x_pred[0], v_pred[0] = xk, vk + + for t in range(horizon - 1): + v_pred[t+1] = v_pred[t] + a[t] * self.dt + x_pred[t+1] = x_pred[t] + v_pred[t] * self.dt + 0.5 * a[t] * self.dt**2 + + tracking_error = np.mean((x_pred - x_ref_np)**2) + control_effort = np.mean(u_current**2) + + print(f" Iter {iteration+1:3d}/{self.linearization_iters}: u[0]={u_current[0]:.4f}, " + f"tracking_err={tracking_error:.4f}, control_effort={control_effort:.4f}") + + return float(u_current[0]) + + +# Example usage +if __name__ == "__main__": + # This is just for illustration + print("MPC with CVXPY - Example usage:") + print(""" + mpc = MPCCVXPY( + accel_model=your_trained_model, + dt=0.1, + horizon=10, + nb_steps=50, + Q_weight=0.1, + R_weight=10.0, + use_ekf=True + ) + + # Generate reference trajectory + x_ref = np.linspace(0, 10, 50) + + # Run MPC + u_history = mpc.solve(x_ref=x_ref, x0=0.0, v0=0.0, verbose=True) + """) \ No newline at end of file diff --git a/src/dronecontrol/commande/MPC_torch.py b/src/dronecontrol/commande/MPC_torch.py new file mode 100644 index 0000000..4d99cfa --- /dev/null +++ b/src/dronecontrol/commande/MPC_torch.py @@ -0,0 +1,208 @@ +from typing import Optional, Tuple +import torch + +from dronecontrol.commande.base_mpc import MPC, ArrayLike +from dronecontrol.models.base_module import BaseModel + + + +class MPCTorch(MPC): + """ + Model Predictive Control using PyTorch optimization. + + Uses gradient descent (Adam) to optimize control sequence. + """ + + def __init__( + self, + accel_model: BaseModel, + dt: float, + horizon: int, + Q: ArrayLike, # Control effort weight matrix + R: ArrayLike, # Tracking error weight matrix + S: ArrayLike, # Speed overshoot weight matrix + tau: float, # Speed overshoot weight + lr: float = 0.01, + max_epochs: int = 50, + u_min: float = -5.0, + u_max: float = 5.0, + use_ekf: bool = False, + use_simulink: bool = False, + optimize_trajectory: bool = False, + max_accel: float = 9.81, + smoothing: bool = True, + smoothing_alpha: float = 0.3, + ): + """ + Initialize MPC controller. + + Args: + accel_model: Neural network predicting acceleration + dt: Time step + horizon: Prediction horizon + nb_steps: Total number of control steps + Q: Weight matrix on control effort + R: Weight matrix on tracking error + lr: Learning rate for Adam optimizer (not used for LBFGS) + max_epochs: Number of optimization iterations per step + u_min, u_max: Control bounds + use_ekf: Whether to use EKF for state estimation + use_simulink: Whether to use Simulink for simulation + optimizer_type: Type of optimizer ("adam" or "lbfgs") + """ + # Call parent constructor + super().__init__( + accel_model=accel_model, + dt=dt, + horizon=horizon, + Q=Q, + R=R, + u_min=u_min, + u_max=u_max, + use_ekf=use_ekf, + use_simulink=use_simulink, + optimize_trajectory=optimize_trajectory, + max_accel=max_accel, + smoothing=smoothing, + smoothing_alpha=smoothing_alpha, + ) + + self.lr = lr + self.max_epochs = max_epochs + self.early_stop_tol = 1e-5 + self.early_stop_patience = 10 + # Store scalar weights for loss computation + self.Q = torch.tensor(Q, dtype=torch.float32, device="cuda" if torch.cuda.is_available() else "cpu") + self.R = torch.tensor(R, dtype=torch.float32, device="cuda" if torch.cuda.is_available() else "cpu") + self.S = torch.tensor(S, dtype=torch.float32, device="cuda" if torch.cuda.is_available() else "cpu") + self.tau = tau + + def project_control(self, u: torch.Tensor) -> torch.Tensor: + """Project control to satisfy bounds.""" + return torch.clamp(u, self.u_min, self.u_max) + + def optimize_control( + self, + x_ref: torch.Tensor, + u: torch.Tensor, + hk: torch.Tensor, + xk: float, + vk: float, + verbose: bool = False, + ) -> float: + """ + Optimize control sequence over the horizon using PyTorch Adam optimizer. + + Args: + x_ref: Reference trajectory [horizon] + u: Initial control sequence [1, horizon, 1] + hk: Initial hidden state [num_layers, 1, hidden_dim] + xk: Initial position + vk: Initial velocity + verbose: Whether to print optimization progress + + Returns: + u_optimal: Optimized control for first step + """ + device = next(self.accel_model.parameters()).device + horizon = x_ref.shape[0] + # Ensure tensors are on the model's device + x_ref = x_ref.to(device) + hk = hk.to(device) + # Initialize control sequence on device + u = torch.zeros(horizon, 1, requires_grad=True, device=device) + + optimizer = torch.optim.Adam([u], lr=self.lr) + + # Early stopping variables + best_loss = float('inf') + patience_counter = 0 + + # Optimization loop + for epoch in range(self.max_epochs): + optimizer.zero_grad() + + # Compute trajectory and loss + x_pred, v_pred, _ = self._compute_trajectory_wrt_NN( + u, xk, vk, hk + ) + + # Loss: tracking + control effort + + # Control tracking error for trajectory + # ||x - x_ref||_R^2 = (x - x_ref)^T R (x - x_ref) + error = x_pred - x_ref # Adjusted reference + R = self.R[:horizon, :horizon] + tracking_loss = torch.dot(error, R @ error) + + # # Control tracking effort for speed + # # x' = 1/tau * (x_pred - x_ref) + # lag = self.tau * v_pred - (x_pred - x_ref) + # S = self.S[:horizon, :horizon] + # speed_tracking_loss = torch.dot(lag.view(-1), S @ lag.view(-1)) + + # ||u||_Q^2 = u^T Q u + u_flat = u.squeeze(-1) # [horizon] + Q = self.Q[:horizon, :horizon] + control_loss = torch.dot(u_flat, Q @ u_flat) + + loss = tracking_loss + control_loss #+ speed_tracking_loss + + # Backward and step + loss.backward() + optimizer.step() + + # Project to bounds + with torch.no_grad(): + u.data = self.project_control(u.data) + + # Early stopping check + if loss.item() < best_loss - self.early_stop_tol: + best_loss = loss.item() + patience_counter = 0 + else: + patience_counter += 1 + + if patience_counter >= self.early_stop_patience: + if verbose: + print(f" Early stopping at epoch {epoch+1}/{self.max_epochs}") + break + + # Extract optimal control + u_optimal = u.detach()[0, 0].item() + + + return u_optimal + + +# Example usage +if __name__ == "__main__": + print("MPC with PyTorch - Example usage:") + print(""" + mpc = MPCTorch( + accel_model=your_trained_model, + dt=0.1, + horizon=10, + nb_steps=50, + Q_weight=0.1, + R_weight=10.0, + lr=0.01, + max_epochs=50, + use_ekf=True + ) + + # Generate reference trajectory + x_ref = np.linspace(0, 10, 50) + + # Run MPC + u_history, state_ y = mpc.solve( + x_ref=x_ref, + x0=0.0, + v0=0.0, + verbose=True + ) + + # Access detailed states + for state in state_history: + print(state) + """) \ No newline at end of file diff --git a/src/dronecontrol/commande/base_mpc.py b/src/dronecontrol/commande/base_mpc.py new file mode 100644 index 0000000..b00dcae --- /dev/null +++ b/src/dronecontrol/commande/base_mpc.py @@ -0,0 +1,413 @@ +from abc import ABC, abstractmethod +from typing import List, Optional, Union, Tuple, Dict +import torch +import numpy as np + +from dronecontrol.models.base_module import BaseModel +from .neural_ekf import NeuralEKF +from .traj_computer import TrajectoryOptimizer + +from dronecontrol.simulink.simulator import DroneSimulator + + +ArrayLike = Union[torch.Tensor, np.ndarray] + +class MPC(ABC): + + def __init__( + self, + accel_model: BaseModel, + dt: float, + horizon: int, + Q: ArrayLike, + R: ArrayLike, + u_min: float, + u_max: float, + use_ekf: bool = False, + use_simulink: bool = False, + optimize_trajectory: bool = False, + max_accel: float = 9.81, + smoothing: bool = True, + smoothing_alpha: float = 0.3, + ): + """ + Initialize MPC controller. + + Args: + accel_model: Neural network predicting acceleration + dt: Time step + horizon: Prediction horizon + nb_steps: Total number of control steps + Q_weight: Weight on control effort (scalar) + R_weight: Weight on tracking error (scalar) + lr: Learning rate for Adam optimizer + max_epochs: Number of optimization iterations per step + u_min, u_max: Control bounds + use_ekf: Whether to use EKF for state estimation + """ + + self.accel_model = accel_model + self.dt = dt + self.horizon = horizon + self.Q = torch.tensor(Q) + self.R = torch.tensor(R) + self.u_min = u_min + self.u_max = u_max + + self.num_layers = accel_model.num_layers + self.hidden_dim = accel_model.hidden_dim + + self.use_simulink = use_simulink + self.use_ekf = use_ekf + + if use_ekf: + self.ekf = NeuralEKF( + model=accel_model, + hidden_dim=self.hidden_dim, + input_dim=1, + ) + else: + self.ekf = None + + self.simulator = DroneSimulator() + + self.max_accel = max_accel + self.optimize_trajectory = optimize_trajectory + self.smoothing = smoothing + self.smoothing_alpha = smoothing_alpha + + def _compute_trajectory_wrt_NN( + self, + u: torch.Tensor, + xk: torch.Tensor, + vk: torch.Tensor, + hk: torch.Tensor + ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: + """ + Compute predicted trajectory given control sequence. + + Returns: + x: Positions [horizon] + v: Velocities [horizon] + a: Accelerations [horizon] + """ + # Ensure inputs are on the model's device + device = next(self.accel_model.parameters()).device + u = u + hk = hk + + # Predict accelerations + with torch.set_grad_enabled(u.requires_grad): + a, _ = self.accel_model(u, hk) # a: [horizon, 1] + + a = a.squeeze(-1) # [horizon] + horizon = a.shape[0] + + # Integrate dynamics on the same device + x = torch.zeros(horizon, device=device) + v = torch.zeros(horizon, device=device) + + + x[0] = xk + v[0] = vk + + v[1:] = vk + torch.cumsum(a[:-1] * self.dt, dim=0) + x[1:] = xk + torch.cumsum(v[:-1] * self.dt + 0.5 * a[:-1] * self.dt**2, dim=0) + + + return x, v, a + + def _update_states_NN( + self, + u: float, + hk: torch.Tensor, + xk: float, + vk: float, + ) -> tuple[torch.Tensor, float, float, float]: + """ + Compute predicted trajectory given control sequence. + + Returns: + x: Positions [horizon] + v: Velocities [horizon] + a: Accelerations [horizon] + """ + # Predict accelerations on the model's device + device = next(self.accel_model.parameters()).device + u_tensor = torch.tensor(u, device=device).view(-1, 1) # [1, 1] + hk = hk + a, h_new = self.accel_model(u_tensor, hk) + + x_new = xk + vk * self.dt + 0.5 * a.item() * self.dt**2 + v_new = vk + a.item() * self.dt + a_new = a.item() + + return h_new, x_new, v_new, a_new + + def _update_states_simulink( + self, + u: float, + hk: torch.Tensor, + ) -> tuple[torch.Tensor, float, float, float]: + """ + Compute predicted trajectory using Simulink model. + + Returns: + x: Positions [horizon] + v: Velocities [horizon] + a: Accelerations [horizon] + """ + + # Apply control to simulator (4 motors) + + if self.simulator is None: + raise RuntimeError("Simulink simulator not available.") + + + device = next(self.accel_model.parameters()).device + u_tensor = torch.tensor(u, device=device).view(-1, 1) # [1, 1, 1] + hk = hk.to(device) + _, h_new = self.accel_model(u_tensor, hk) + + a_measured = self.simulator.accel(control_input=[u] * 4)[-1] + v_new = self.simulator.vel[-1] + x_new = self.simulator.pos[-1] + + return h_new, x_new, v_new, a_measured + + def update_states( + self, + u: float, + hk: torch.Tensor, + xk: float, + vk: float, + ) -> Dict[str, Tuple[torch.Tensor, float, float, float]]: + """ + Compute predicted trajectory given control sequence. + + Returns: + x: Positions [horizon] + v: Velocities [horizon] + a: Accelerations [horizon] + """ + + # if self.use_simulink: + # return self._update_states_simulink(u, hk) + # else: + # return self._update_states_NN(u, hk, xk, vk) + + return { + "simulink": self._update_states_simulink(u, hk), + "nn": self._update_states_NN(u, hk, xk, vk) + } + + def solve( + self, + x_ref: torch.Tensor, + x0: float, + v0: float, + a0: float = 0.0, + verbose: bool = True, + ) -> Tuple[torch.Tensor, Dict[str, Dict[str, torch.Tensor]]]: + """ + Solve MPC optimization problem. + + Args: + x_ref: Reference trajectory [nb_steps] + x0: Initial position + v0: Initial velocity + verbose: Whether to print progress + return_history: Whether to return optimization history + + Returns: + u_history: Optimized control inputs [nb_steps] + x_history: Predicted positions [nb_steps] + v_history: Predicted velocities [nb_steps] + a_history: Predicted accelerations [nb_steps] + """ + + # Initialize + + x_ref_step = x_ref + + with torch.no_grad(): + + traj_computer = TrajectoryOptimizer( + dt=self.dt, + max_accel=self.max_accel, + x_ref=x_ref, + x0=x0, + smoothing=self.smoothing, + alpha=self.smoothing_alpha, + ) + x_ref, x_ref_step = traj_computer.optimize_trajectory() + + nb_steps = x_ref.shape[0] + + x_current = x0 + u_history = torch.zeros(nb_steps, dtype=torch.float32, requires_grad=False) + h_hist_simulink = torch.zeros((nb_steps, self.num_layers, self.hidden_dim), dtype=torch.float32, requires_grad=False) + x_hist_simulink = torch.zeros(nb_steps, dtype=torch.float32, requires_grad=False) + v_hist_simulink = torch.zeros(nb_steps, dtype=torch.float32, requires_grad=False) + a_hist_simulink = torch.zeros(nb_steps, dtype=torch.float32, requires_grad=False) + + h_hist_nn = torch.zeros((nb_steps, self.num_layers, self.hidden_dim), dtype=torch.float32, requires_grad=False) + x_hist_nn = torch.zeros(nb_steps, dtype=torch.float32, requires_grad=False) + v_hist_nn = torch.zeros(nb_steps, dtype=torch.float32, requires_grad=False) + a_hist_nn = torch.zeros(nb_steps, dtype=torch.float32, requires_grad=False) + x_ref = torch.tensor(x_ref, dtype=torch.float32, requires_grad=False) + + v_current = v0 + a_current = a0 + x_current = x0 + + if self.use_simulink: + initial_state = np.array([x0] + [0.0]*11, dtype=np.float32) # Assuming 6 state variables in total + self.simulator.reset(initial_state) #type: ignore + + h_current = torch.zeros(self.num_layers, self.hidden_dim) + + + for step in range(nb_steps): + + u_opt, state_update_dict = self.step( + x_ref=x_ref[step:step + self.horizon], + hk=h_current, + xk=x_current, + vk=v_current, + verbose=verbose + ) + + u_history[step] = u_opt + + h_hist_simulink[step], x_hist_simulink[step], v_hist_simulink[step], a_hist_simulink[step] = state_update_dict["simulink"] + h_hist_nn[step], x_hist_nn[step], v_hist_nn[step], a_hist_nn[step] = state_update_dict["nn"] + + key = "simulink" if self.use_simulink else "nn" + h_current, x_current, v_current, a_current = state_update_dict[key] + + # Compute tracking error + tracking_error = abs(x_current - x_ref[step]) if step < len(x_ref) else 0.0 + + if verbose: + if step % 5 == 0: # Print every 5 steps + print(f"Step {step:3d}/{nb_steps} | " + f"x={x_current:7.3f} (ref={x_ref[step]:7.3f}) | " + f"v={v_current:7.3f} | " + f"a={a_current:7.3f} | " + f"u={u_opt:7.3f} | " + f"err={tracking_error:7.4f}") + + h_current = h_current.detach() + # Apply EKF if enabled + if self.use_ekf and self.ekf is not None: + # EKF update + + + h_current = self.ekf.step( + u=torch.tensor(u_opt, dtype=torch.float32).view(1, 1), + hk=h_current, + a_measured=torch.tensor(a_current, dtype=torch.float32) + ) + + + histories = { + "simulink": { + "h": h_hist_simulink, + "x": x_hist_simulink, + "v": v_hist_simulink, + "a": a_hist_simulink + }, + "nn": { + "h": h_hist_nn, + "x": x_hist_nn, + "v": v_hist_nn, + "a": a_hist_nn + }, + "references": + {"steps" : x_ref_step, + "filtered": x_ref + } + } + + return u_history, histories + + + @abstractmethod + def optimize_control( + self, + x_ref: torch.Tensor, + u: torch.Tensor, + hk: torch.Tensor, + xk: float, + vk: float, + verbose: bool = False + ) -> float: + """ + Optimize control sequence over the horizon. + + Args: + u: Initial control sequence [1, horizon, 1] + hidden: Initial hidden state [1, 1, hidden_dim] + x_ref: Reference trajectory [horizon] + x0: Initial position + v0: Initial velocity + verbose: Whether to print optimization progress + + Returns: + u_opt: optimized control sequence + """ + pass # To be implemented in subclasses + + + + def step( + self, + x_ref: torch.Tensor, + hk: torch.Tensor, + xk: float, + vk: float, + verbose: bool = False + ) -> Tuple[float,dict]: + """ + Optimize control for one MPC step. + + Args: + x_ref: Reference trajectory [horizon] + x0: Current position + v0: Current velocity + hidden: Hidden state [1, 1, hidden_dim] + verbose: Print optimization progress + + Returns: + u_optimal: Optimal control for first step + info: Dictionary with optimization info + """ + # Detach inputs + hk = hk.detach() + x_ref = x_ref.detach() + + horizon = x_ref.shape[0] + + # Initialize control sequence + u = torch.zeros(horizon, 1, requires_grad=True) + + u_optimal = self.optimize_control( + x_ref=x_ref, + u=u, + hk=hk, + xk=xk, + vk=vk, + verbose=verbose + ) + + + + state_update_dict = self.update_states( + u=u_optimal, + xk=xk, + vk=vk, + hk=hk + ) + + return u_optimal, state_update_dict \ No newline at end of file diff --git a/src/dronecontrol/commande/neural_ekf.py b/src/dronecontrol/commande/neural_ekf.py new file mode 100644 index 0000000..b3a1fd7 --- /dev/null +++ b/src/dronecontrol/commande/neural_ekf.py @@ -0,0 +1,176 @@ +import numpy as np +import torch +from typing import Union + +from dronecontrol.models.base_module import BaseModel + +ArrayLike = Union[np.ndarray, torch.Tensor] + +class NeuralEKF: + """ + Extended Kalman Filter wrapper for a neural network dynamics model. + """ + + def __init__( + self, + model: BaseModel, + hidden_dim: int, + input_dim: int, + Q_weight: float = 1.0, + R_weight: float = 1.0 + ): + + self.model = model + self.hidden_dim = hidden_dim + self.input_dim = input_dim + self.device = next(model.parameters()).device + + # État de l'EKF (sans filterpy) + self.h = torch.zeros(hidden_dim, device=self.device) # État caché estimé + self.P = torch.eye(hidden_dim, device=self.device) * 0.1 # Covariance de l'état + self.Q = torch.eye(hidden_dim, device=self.device) * Q_weight # Bruit de processus + self.R = torch.tensor(input_dim, device=self.device, dtype=torch.float32) * R_weight # Bruit de mesure + + def state_transition(self, u: torch.Tensor, h: torch.Tensor) -> torch.Tensor: + """State transition: h_k+1 = f(h_k, u_k) using the neural network.""" + h_t = h + u_t = u + + with torch.no_grad(): + _, h_next = self.model(u_t, h_t) + + return h_next + + def measurement_function(self, u: torch.Tensor, h: torch.Tensor) -> torch.Tensor: + """Measurement function: a_k = h(h_k, u_k) - predict accel from hidden state.""" + h_t = h + u_t = u + + with torch.no_grad(): + a_hat, _ = self.model(u_t, h_t) + + return a_hat + + def compute_H(self, u: torch.Tensor, h: torch.Tensor) -> torch.Tensor: + + """ + Compute Jacobian of measurement function with respect to hidden state h. + """ + h_t = h.to(self.device).requires_grad_(True) + u_t = u + + def func(h): + a_hat, _ = self.model(u_t, h) + return a_hat + + H = torch.autograd.functional.jacobian(func, h_t) + + return H + + + def compute_F(self, u: torch.Tensor, h: torch.Tensor) -> torch.Tensor: + """ + Compute Jacobian of func with respect to hidden state h. + """ + device = next(self.model.parameters()).device + h = h.to(device).requires_grad_(True) + u = u.to(device) + + def func(h): + _, h_next = self.model(u, h) + return h_next + + F = torch.autograd.functional.jacobian(func, h) + + return F + + def step(self, u: torch.Tensor, hk: torch.Tensor, a_measured: torch.Tensor) -> torch.Tensor: + """ + Perform one EKF step (predict + update) starting from a given hidden state. + EKF is applied only on the last layer's hidden state. + """ + + # hk shape: (num_layers, hidden_dim) + # Extract last layer for EKF + num_layers = hk.shape[0] if hk.dim() > 1 else 1 + if hk.dim() > 1: + h_last = hk[-1].to(self.device) # Shape: (hidden_dim,) + else: + h_last = hk.to(self.device) + + self.h = h_last + + # ===== PREDICT STEP ===== + # For multi-layer, we need to compute jacobian w.r.t last layer only + F = self.compute_F(u, hk) # Jacobian of last layer + + # Extract last layer jacobian if needed + if F.dim() == 4: # (num_layers, hidden_dim, num_layers, hidden_dim) + F = F[-1, :, -1, :] # (hidden_dim, hidden_dim) + elif F.dim() == 2: + pass # Already (hidden_dim, hidden_dim) + + h_pred_full = self.state_transition(u, hk) # Full state transition + if h_pred_full.dim() > 1: + h_pred = h_pred_full[-1] # Last layer only + else: + h_pred = h_pred_full + + P_pred = F @ self.P @ F.T + self.Q + + # ===== UPDATE STEP ===== + H = self.compute_H(u, hk if hk.dim() > 1 else hk.unsqueeze(0)) + + # Extract jacobian w.r.t last layer + if H.dim() == 4: # (output_dim, num_layers, hidden_dim) + H = H[0, :, -1, :] # (output_dim, hidden_dim) + + z_pred = self.measurement_function(u, hk if hk.dim() > 1 else hk.unsqueeze(0)) + + # Innovation + y = a_measured.to(self.device) - z_pred + if y.dim() == 0: + y = y.unsqueeze(0) + + # Innovation covariance + if self.R.dim() == 0: + R_mat = self.R.unsqueeze(0).unsqueeze(0) + else: + R_mat = self.R if self.R.dim() == 2 else torch.diag(self.R) + + S = H @ P_pred @ H.T + R_mat + + # Kalman gain + K = P_pred @ H.T @ torch.linalg.inv(S) + + # Update last layer state + self.h = h_pred.unsqueeze(-1) + K @ y + self.h = self.h.squeeze(-1) + + # Update covariance + I = torch.eye(self.hidden_dim, device=self.device) + self.P = (I - K @ H) @ P_pred + + # Reconstruct full hidden state with updated last layer + if hk.dim() > 1 and num_layers > 1: + h_updated = h_pred_full.clone() + h_updated[-1] = self.h + return h_updated.detach() + else: + return self.h.unsqueeze(0).detach() if num_layers > 1 else self.h.detach() + + def reset(self, x0=None): + """Reset EKF state.""" + if x0 is None: + self.h = torch.zeros(self.hidden_dim, device=self.device) + else: + self.h = torch.as_tensor(x0, device=self.device, dtype=torch.float32) + self.P = torch.eye(self.hidden_dim, device=self.device) * 0.1 + + def get_state(self) -> torch.Tensor: + """Get current hidden state estimate.""" + return self.h.clone() + + def get_covariance(self) -> torch.Tensor: + """Get current state covariance matrix.""" + return self.P.clone() \ No newline at end of file diff --git a/src/dronecontrol/commande/traj_computer.py b/src/dronecontrol/commande/traj_computer.py new file mode 100644 index 0000000..ac799cc --- /dev/null +++ b/src/dronecontrol/commande/traj_computer.py @@ -0,0 +1,70 @@ +import torch +from typing import Tuple + + +class TrajectoryOptimizer: + + def __init__( + self, + dt: float, + max_accel: float, + x_ref: torch.Tensor, + x0: float, + smoothing: bool = True, + alpha: float = 0.3, + stable_time: float = 7, + ): + self.dt = dt + self.max_accel = max_accel + self.x_ref = x_ref + self.x0 = x0 + self.smoothing = smoothing + self.alpha = alpha + self.nb_stable_steps = int(stable_time / dt) + + def _get_nb_steps(self, x, x0) -> int: + """Compute number of steps from horizon and dt.""" + return int(abs(x - x0) / (self.max_accel * self.dt)) + 1 + + @staticmethod + def exponential_moving_average(x: torch.Tensor, alpha: float) -> torch.Tensor: + y = torch.zeros_like(x) + y[0] = x[0] + for i in range(1, len(x)): + y[i] = alpha * x[i] + (1 - alpha) * y[i - 1] + return y + + + def optimize_trajectory(self) -> Tuple[torch.Tensor, torch.Tensor]: + """ + Optimize trajectory through all reference points in x_ref. + Creates a trajectory from x0 -> x_ref[0] -> x_ref[1] -> ... -> x_ref[-1] + """ + trajectories = [] + steps = [] + current_pos = self.x0 + + # Iterate through each reference point + for i in range(self.x_ref.shape[0]): + x_target = self.x_ref[i].item() + nb_steps = self._get_nb_steps(x_target, current_pos) * 3 + # Create trajectory segment from current position to target + segment = torch.ones((nb_steps + self.nb_stable_steps), dtype=torch.float32) + + segment[0] = current_pos + segment[1:] *= x_target + + if self.smoothing: + smoothed_segment = self.exponential_moving_average(segment, alpha=self.alpha) + trajectories.append(smoothed_segment) + else: + trajectories.append(segment) + steps.append(segment) + # Update current position for next segment + current_pos = x_target + + # Concatenate all trajectory segments + full_trajectory = torch.cat(trajectories) + x_ref_step = torch.cat(steps) + + return full_trajectory, x_ref_step \ No newline at end of file diff --git a/src/control_validation.py b/src/dronecontrol/control_validation.py similarity index 98% rename from src/control_validation.py rename to src/dronecontrol/control_validation.py index e7a095b..2286a57 100644 --- a/src/control_validation.py +++ b/src/dronecontrol/control_validation.py @@ -10,7 +10,7 @@ import numpy as np import torch -from .data_loader import load_npz +from dronecontrol.data_process.data_loader import load_npz from .models import MODEL_REGISTRY LOGGER = logging.getLogger(__name__) diff --git a/src/data_acquisition.py b/src/dronecontrol/data_process/data_acquisition.py similarity index 98% rename from src/data_acquisition.py rename to src/dronecontrol/data_process/data_acquisition.py index b46d959..df11d57 100644 --- a/src/data_acquisition.py +++ b/src/dronecontrol/data_process/data_acquisition.py @@ -10,7 +10,7 @@ import numpy as np import pandas as pd -from .utils import ensure_dir, resolve_path +from dronecontrol.utils import ensure_dir, resolve_path LOGGER = logging.getLogger(__name__) diff --git a/src/dronecontrol/data_process/data_augmentation.py b/src/dronecontrol/data_process/data_augmentation.py new file mode 100644 index 0000000..241d2a3 --- /dev/null +++ b/src/dronecontrol/data_process/data_augmentation.py @@ -0,0 +1,47 @@ +"""Data augmentation helpers for UAV control pipeline.""" + +from __future__ import annotations + +import logging +from typing import Dict, Optional, Tuple + +import numpy as np +import pandas as pd +from abc import ABC, abstractmethod + + +LOGGER = logging.getLogger(__name__) + + + + +class DataAugmenter(ABC): + """Base class for data augmentation strategies.""" + + @abstractmethod + def augment( + self, + input_data: np.ndarray, + target_data: np.ndarray, + ) -> Tuple[np.ndarray, np.ndarray]: + """Augment the input and target data. + + Args: + input_data: Original input data. + target_data: Original target data. + + Returns: + A tuple of augmented input and target data. + """ + pass + + +class NoOpAugmenter(DataAugmenter): + """No-operation augmenter that returns data unchanged.""" + + def augment( + self, + input_data: np.ndarray, + target_data: np.ndarray, + ) -> Tuple[np.ndarray, np.ndarray]: + return input_data, target_data \ No newline at end of file diff --git a/src/dronecontrol/data_process/data_cleaning.py b/src/dronecontrol/data_process/data_cleaning.py new file mode 100644 index 0000000..e1c5328 --- /dev/null +++ b/src/dronecontrol/data_process/data_cleaning.py @@ -0,0 +1,118 @@ +import logging +from tqdm import tqdm +import pandas as pd +import numpy as np +from typing import Any, Tuple +from pathlib import Path +from sklearn.preprocessing import StandardScaler + +from abc import ABC, abstractmethod + +LOGGER = logging.getLogger(__name__) + + +class DataCleaner(ABC): + """Base class for loading and cleaning data from csvs.""" + + def __init__(self, input_fp: str, output_fp: str): + input_df = pd.read_csv(input_fp, header=None) + output_df = pd.read_csv(output_fp, header=None) + scaled_input = StandardScaler().fit_transform(input_df) + self.input_df = pd.DataFrame(scaled_input) + self.output_df = output_df + + @abstractmethod + def get_clean_data(self) -> Tuple[np.ndarray, np.ndarray]: + pass + +class AVDataCleaner(DataCleaner): + def __init__(self, input_fp: str, output_fp: str): + self.input_df = pd.read_csv(input_fp, header=None) + self.output_df = pd.read_csv(output_fp, header=None) + + def calculate_energy(self, row : pd.Series) -> float: + return np.sum(np.square(row)) + + def get_clean_data(self) -> Tuple[np.ndarray, np.ndarray]: + + eps = 1e-6 # Small constant to avoid division by zero + + input_energy = (self.input_df.to_numpy()**2).sum(axis=1) + output_energy = (self.output_df.to_numpy()**2).sum(axis=1) + ratio = output_energy / (input_energy + eps) + + mean_ratio = np.nanmean(ratio) + std_ratio = np.nanstd(ratio) + + sigma = 1/4 # Corresponds to 95% confidence interval + + mask = abs(ratio - mean_ratio) <= sigma * std_ratio + + # Application du masque sur les deux DataFrames + self.input_df = self.input_df[mask].reset_index(drop=True) + self.output_df = self.output_df[mask].reset_index(drop=True) + + print(f"✓ Filtered {len(mask) - np.sum(mask)} samples based on energy ratio") + + return self.input_df.to_numpy(), self.output_df.to_numpy() + +def csv_to_tensor(csv_path: str | Path) -> np.ndarray: + """ + Convertit un CSV concaténé avec colonne 'run_id' en tenseur numpy [run, time, dim]. + Hypothèse: tous les runs ont le même nombre d'échantillons. + """ + csv_path = Path(csv_path) + df = pd.read_csv(csv_path) + + if "run_id" not in df.columns: + raise ValueError(f"{csv_path} doit contenir une colonne 'run_id'.") + + # Trie par run_id pour reshaper proprement + df = df.sort_values(["run_id"], kind="stable").reset_index(drop=True) + + runs = df["run_id"].unique() + runs_sorted = np.sort(runs) + n_runs = len(runs_sorted) + + # nombre d'échantillons par run (on prend le premier) + n_time = df[df["run_id"] == runs_sorted[0]].shape[0] + data_cols = [c for c in df.columns if c != "run_id"] + n_dim = len(data_cols) + + # Vérif que toutes les longueurs sont identiques + if not all(df[df["run_id"] == r].shape[0] == n_time for r in runs_sorted): + raise ValueError("Tous les runs n'ont pas la même longueur.") + + data_flat = df.loc[:, data_cols].to_numpy(dtype=float) + tensor = data_flat.reshape(n_runs, n_time, n_dim) + return tensor + + + +class FullMotorDataCleaner(DataCleaner): + """ + Lit les 3 CSV concaténés (avec 'run_id'): _inputs.csv, _states.csv, _derivatives.csv + et donne accès aux tenseurs numpy: + inputs -> (n_runs, n_time, 4) + states -> (n_runs, n_time+1, 12) # attention: +1 si tu as sauvegardé N+1 états ! + derivs -> (n_runs, n_time, 12) + """ + def __init__(self, any_fp_under_data_dir: str, _unused_output_fp: str | None = None): + data_root = Path(any_fp_under_data_dir).parent # ex: .../control_4_motors/ + inputs_fp = data_root / "_inputs.csv" + states_fp = data_root / "_states.csv" + derivs_fp = data_root / "_derivatives.csv" + + # Lis avec header=0 (par défaut), car tes CSV ont un header ("run_id, ...") + self.inputs_df = pd.read_csv(inputs_fp) + self.states_df = pd.read_csv(states_fp) + self.derivs_df = pd.read_csv(derivs_fp) + + # Tenseurs pré-calculés + self.inputs_tensor = csv_to_tensor(inputs_fp) # (n_runs, n_time, 4) + # _states.csv contient N+1 lignes par run -> on reconstruit pareil + self.states_tensor = csv_to_tensor(states_fp) # (n_runs, n_time_states, 12) + self.derivs_tensor = csv_to_tensor(derivs_fp) # (n_runs, n_time, 12) + + def get_clean_data(self) -> Tuple[np.ndarray, np.ndarray]: + return self.inputs_tensor, self.derivs_tensor \ No newline at end of file diff --git a/src/dronecontrol/data_process/data_loader.py b/src/dronecontrol/data_process/data_loader.py new file mode 100644 index 0000000..838f9a8 --- /dev/null +++ b/src/dronecontrol/data_process/data_loader.py @@ -0,0 +1,87 @@ +from __future__ import annotations + +import numpy as np +import pytorch_lightning as pl +from sklearn.model_selection import train_test_split +from torch.utils.data import DataLoader + +from .dataset import AVDataset + +class AVDataLoader(pl.LightningDataModule): + def __init__( + self, + input: np.ndarray, + target: np.ndarray, + batch_size: int, + val_split: float = 0.2, + test_split: float = 0.1, + seed: int | None = None, + ) -> None: + + super().__init__() + self.input = input + self.target = target + self.batch_size = batch_size + self.val_split = val_split + self.test_split = test_split + self.seed = seed if seed is not None else 42 + + self.train_dataset = None + self.val_dataset = None + self.test_dataset = None + + def setup(self, stage: str | None = None) -> None: + + # First split: train + val vs test + input_train_val, input_test, target_train_val, target_test = train_test_split( + self.input, + self.target, + test_size=self.test_split, + random_state=self.seed, + shuffle=False, + ) + + # Second split: train vs val + val_size_adjusted = self.val_split / (1 - self.test_split) + input_train, input_val, target_train, target_val = train_test_split( + input_train_val, + target_train_val, + test_size=val_size_adjusted, + random_state=self.seed, + shuffle=False, + ) + + # Create datasets + self.train_dataset = AVDataset(input=input_train, target=target_train) + self.val_dataset = AVDataset(input=input_val, target=target_val) + self.test_dataset = AVDataset(input=input_test, target=target_test) + + def train_dataloader(self) -> DataLoader: + if self.train_dataset is None: + raise RuntimeError("Data module not set up before accessing train_dataloader") + return DataLoader( + self.train_dataset, + batch_size=self.batch_size, + num_workers=8 + ) + + def val_dataloader(self) -> DataLoader: + if self.val_dataset is None: + raise RuntimeError("Data module not set up before accessing val_dataloader") + return DataLoader( + self.val_dataset, + batch_size=self.batch_size, + num_workers=8, + persistent_workers=True + ) + + def test_dataloader(self) -> DataLoader: + if self.test_dataset is None: + raise RuntimeError("Data module not set up before accessing test_dataloader") + return DataLoader( + self.test_dataset, + batch_size=self.batch_size, + num_workers=8 + ) + + diff --git a/src/dronecontrol/data_process/dataset.py b/src/dronecontrol/data_process/dataset.py new file mode 100644 index 0000000..a3fa950 --- /dev/null +++ b/src/dronecontrol/data_process/dataset.py @@ -0,0 +1,29 @@ +from torch.utils.data import Dataset +import numpy as np +import torch + +class AVDataset(Dataset): + + def __init__( + self, + input: np.ndarray, + target: np.ndarray + ) -> None: + + super().__init__() + + self.input = input + self.target = target + + def __len__(self) -> int: + return len(self.input) + + def __getitem__(self, idx: int): + if torch.tensor(self.input[idx], dtype=torch.float32).ndim == 1: + input_tensor = torch.tensor(self.input[idx], dtype=torch.float32).unsqueeze(-1) + target_tensor = torch.tensor(self.target[idx], dtype=torch.float32).unsqueeze(-1) + else: + input_tensor = torch.tensor(self.input[idx], dtype=torch.float32) + target_tensor = torch.tensor(self.target[idx], dtype=torch.float32) + + return input_tensor, target_tensor \ No newline at end of file diff --git a/src/dronecontrol/data_process/preparation.py b/src/dronecontrol/data_process/preparation.py new file mode 100644 index 0000000..f137e41 --- /dev/null +++ b/src/dronecontrol/data_process/preparation.py @@ -0,0 +1,72 @@ +from __future__ import annotations + +from pathlib import Path +from typing import Any, Dict, Tuple + +import pytorch_lightning as pl + +from .data_cleaning import AVDataCleaner, FullMotorDataCleaner +from .data_loader import AVDataLoader +from .data_augmentation import DataAugmenter, NoOpAugmenter + + +SCENARIOS: Dict[str, Dict[str, Any]] = { + "accel_vs_voltage": { + "data_cleaner": AVDataCleaner, + "data_loader": AVDataLoader, + }, + "control_4_motors" : { + "data_cleaner": FullMotorDataCleaner, + "data_loader": AVDataLoader, + } +} + + +def prepare_scenario_data( + scenario_conf: Dict[str, Any], + project_root: Path, +) -> Tuple[pl.LightningDataModule, Dict[str, Any]]: + scenario_cfg = SCENARIOS.get(scenario_conf["name"]) + if scenario_cfg is None: + raise ValueError(f"Unsupported scenario type: {scenario_conf['name']}") + + cleaner_cls = scenario_cfg["data_cleaner"] + loader_cls = scenario_cfg["data_loader"] + augmenter_cls: type[DataAugmenter] = scenario_cfg.get("data_augmenter", NoOpAugmenter) + + data_cfg = scenario_conf.get("data", {}) + training_cfg = scenario_conf.get("training", {}) + + input_path = project_root / data_cfg["input_file"] + output_path = project_root / data_cfg["output_file"] + + cleaner = cleaner_cls(str(input_path), str(output_path)) + raw_input, raw_target = cleaner.get_clean_data() + + augmenter = augmenter_cls() + augmented_input, augmented_target = augmenter.augment(raw_input, raw_target) + + + + batch_size = int(training_cfg.get("batch_size", 32)) + val_split = float(training_cfg.get("val_split", 0.2)) + test_split = float(training_cfg.get("test_split", 0.1)) + seed = int(training_cfg.get("seed", 42)) + + data_module = loader_cls( + augmented_input, + augmented_target, + batch_size=batch_size, + val_split=val_split, + test_split=test_split, + seed=seed, + ) + + metadata = { + "input_dim": data_cfg["input_dim"], + "output_dim": data_cfg["output_dim"], + "num_samples": int(augmented_input.shape[0]), + } + + return data_module, metadata + diff --git a/src/dronecontrol/globals.py b/src/dronecontrol/globals.py new file mode 100644 index 0000000..ea6902d --- /dev/null +++ b/src/dronecontrol/globals.py @@ -0,0 +1,6 @@ +from pathlib import Path + +BASEDIR = Path(__file__).parent.parent.parent +INPUT_DATA = BASEDIR / "data"/ "accel_vs_voltage" / "in.csv" +OUTPUT_DATA = BASEDIR / "data"/ "accel_vs_voltage" / "out.csv" + diff --git a/src/dronecontrol/model_training.py b/src/dronecontrol/model_training.py new file mode 100644 index 0000000..f32f6e0 --- /dev/null +++ b/src/dronecontrol/model_training.py @@ -0,0 +1,139 @@ +"""Scenario training orchestration built on PyTorch Lightning.""" + +from __future__ import annotations + +import logging +from pathlib import Path +from typing import Any, Dict, Iterable, List, Tuple + +import pytorch_lightning as pl +from pytorch_lightning.callbacks import EarlyStopping, ModelCheckpoint +from pytorch_lightning.loggers import CSVLogger + +from .models import MODEL_REGISTRY + +LOGGER = logging.getLogger(__name__) + +DEFAULT_MODEL_PARAMS: Dict[str, Dict[str, Any]] = {} + + +def _resolve_accelerator(device_pref: str) -> Tuple[str, str | int]: + device_pref = device_pref.lower() + if device_pref == "cpu": + return "cpu", 1 + if device_pref == "gpu": + return "gpu", "auto" + return "auto", "auto" + + +def _collect_model_params(model_name: str, training_cfg: Dict[str, Any]) -> Dict[str, Any]: + params = training_cfg.get("model_params", {}).get(model_name, {}) + overrides = training_cfg.get("model_params", {}).get(model_name, {}) + params.update(overrides) + return params + + +def train_models_for_scenario( + scenario_name: str, + scenario_cfg: Dict[str, Any], + general_cfg: Dict[str, Any], + data_module: pl.LightningDataModule, + input_dim: int, + output_dim: int, +) -> List[Tuple[str, Path]]: + training_cfg = dict(scenario_cfg.get("training", {}) or {}) + models: Iterable[str] = training_cfg.get("models", ["gru"]) + models = [model.lower() for model in models] + + if not models: + LOGGER.warning("No models configured for scenario %s", scenario_name) + return [] + + lr = float(training_cfg["lr"]) + epochs = int(training_cfg["epochs"]) + seed = int(training_cfg["seed"]) + patience = int(training_cfg["early_stopping_patience"]) + + pl.seed_everything(seed, workers=True) + + checkpoint_root = Path(general_cfg.get("checkpoint_dir", "models")) / scenario_name + checkpoint_root.mkdir(parents=True, exist_ok=True) + + log_root = Path(general_cfg.get("log_dir", "logs")) / scenario_name + log_root.mkdir(parents=True, exist_ok=True) + + accelerator, devices = _resolve_accelerator(str(general_cfg.get("device", "auto"))) + deterministic = bool(general_cfg.get("deterministic", True)) + + trained_models: List[Tuple[str, Path]] = [] + + for model_name in models: + model_cls = MODEL_REGISTRY.get(model_name) + if model_cls is None: + LOGGER.error("Unknown model '%s' requested for scenario %s", model_name, scenario_name) + continue + + model_params = _collect_model_params(model_name, training_cfg) + scheduler_cfg = training_cfg.get("scheduler", {}) + scheduler_type = scheduler_cfg.get("type") + scheduler_kwargs = scheduler_cfg.get("params", {}) + model = model_cls( + input_dim, + output_dim, + lr=lr, + scheduler_type=scheduler_type, + scheduler_kwargs=scheduler_kwargs, + **model_params, + ) + + checkpoint_dir = checkpoint_root / model_name + checkpoint_dir.mkdir(parents=True, exist_ok=True) + + checkpoint_callback = ModelCheckpoint( + dirpath=checkpoint_dir, + filename="{epoch:03d}-{val_loss:.4f}", + monitor="val_loss", + mode="min", + save_top_k=1, + ) + early_stopping = EarlyStopping( + monitor="val_loss", + patience=patience, + mode="min", + verbose=False, + ) + csv_logger = CSVLogger(save_dir=log_root, name=model_name) + + trainer = pl.Trainer( + max_epochs=epochs, + accelerator=accelerator, + devices=devices, + callbacks=[checkpoint_callback, early_stopping], + logger=csv_logger, + deterministic=deterministic, + log_every_n_steps =10, + enable_progress_bar=True, + ) + + LOGGER.info( + "Training %s model for scenario %s (epochs=%d, lr=%.3g)", + model_name, + scenario_name, + epochs, + lr, + ) + + trainer.fit(model, datamodule=data_module) + trainer.test(model, datamodule=data_module) + trainer.validate(model, datamodule=data_module) + trainer.test(model, datamodule=data_module) + + best_ckpt = checkpoint_callback.best_model_path + if best_ckpt: + ckpt_path = Path(best_ckpt) + trained_models.append((model_name, ckpt_path)) + LOGGER.info("Best %s checkpoint stored at %s", model_name, ckpt_path) + else: + LOGGER.warning("No checkpoint produced for model %s in scenario %s", model_name, scenario_name) + + return trained_models diff --git a/src/dronecontrol/models/__init__.py b/src/dronecontrol/models/__init__.py new file mode 100644 index 0000000..0e6bc9a --- /dev/null +++ b/src/dronecontrol/models/__init__.py @@ -0,0 +1,21 @@ +"""Model registry for UAV control.""" + +from .linear_module import LinearModel +from .rnn_module import RNN +from .gru_module import GRU +from .lstm import LSTM +from .rnn import model_nn + +MODEL_REGISTRY = { + "linear": LinearModel, + "rnn": model_nn, + "gru": GRU, + "lstm": LSTM, +} + +__all__ = [ + "LinearModel", + "RNN", + "GRU", + "LSTM", +] diff --git a/src/dronecontrol/models/base_module.py b/src/dronecontrol/models/base_module.py new file mode 100644 index 0000000..a4a4c5f --- /dev/null +++ b/src/dronecontrol/models/base_module.py @@ -0,0 +1,118 @@ +"""Base Lightning module definitions.""" + +from typing import Optional +from abc import ABC, abstractmethod + +import pytorch_lightning as pl +import torch +from torch import nn +import matplotlib.pyplot as plt +from pathlib import Path + + +class BaseModel(pl.LightningModule, ABC): + + def __init__( + self, + input_dim: int, + output_dim: int, + hidden_dim: int, + lr: float, + num_layers: int = 1, + scheduler_type: Optional[str] = "ReduceLROnPlateau", + scheduler_kwargs: Optional[dict] = None + ): + + super().__init__() + self.input_dim = input_dim + self.output_dim = output_dim + self.hidden_dim = hidden_dim + self.num_layers = num_layers + self.save_hyperparameters() + self.input_dim = input_dim + self.output_dim = output_dim + self.lr = lr + self.scheduler_type = scheduler_type + self.scheduler_kwargs = scheduler_kwargs or {} + self.mse_loss = nn.MSELoss() + + @abstractmethod + def forward(self, u: torch.Tensor, hidden_state: Optional[torch.Tensor]) -> torch.Tensor: # pragma: no cover - abstract + pass + + def training_step(self, batch, batch_idx): # type: ignore[override] + x, y = batch + y_hat, _ = self(x) + loss = self.mse_loss(y_hat[:, 1:, :], y[:, 1:, :])# we dont take into account the first timestep, as we cant predict without prior info + self.log("train_loss", loss, prog_bar=True) + return loss + + def validation_step(self, batch, batch_idx): # type: ignore[override] + x, y = batch + y_hat, _ = self(x) + loss = self.mse_loss(y_hat[:, 1:, :], y[:, 1:, :]) # we dont take into account the first timestep + self.log("val_loss", loss, prog_bar=True) + return loss + + def test_step(self, batch, batch_idx): # type: ignore[override] + x, y = batch + y_hat, _ = self(x) + loss = self.mse_loss(y_hat[:, 1:, :], y[:, 1:, :]) # we dont take into account the first timestep + self.log("test_loss", loss) + + # Generate and save plots + fig = self.plot_predictions(y[0, :1, :].squeeze(-1), y_hat[0, :1, :].squeeze(-1)) + log_dir = Path("predictions_plots") + log_dir.mkdir(parents=True, exist_ok=True) + fig.savefig(log_dir / f"test_predictions_batch_{batch_idx}.png", dpi=100) # type: ignore[attr-defined] + plt.close(fig) + + return loss + + def configure_optimizers(self): # type: ignore[override] + optimizer = torch.optim.Adam(self.parameters(), lr=self.lr) + if self.scheduler_type: + if self.scheduler_type == "ReduceLROnPlateau": + scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, **self.scheduler_kwargs) + return { + "optimizer": optimizer, + "lr_scheduler": { + "scheduler": scheduler, + "monitor": "val_loss", + "interval": "epoch", + "frequency": 1, + }, + } + return optimizer + + def plot_predictions(self, y_true: torch.Tensor, y_pred: torch.Tensor): + """Generate plots comparing predictions with ground truth.""" + y_true_np = y_true.cpu().detach().numpy() + y_pred_np = y_pred.cpu().detach().numpy() + + # Calculate error + error = y_pred_np - y_true_np + + fig, axes = plt.subplots(2, 1, figsize=(12, 8)) + + # Plot 1: Error of model output vs true y + axes[0].plot(error, label='Prediction Error', linewidth=1.5, alpha=0.7) + axes[0].axhline(y=0, color='r', linestyle='--', linewidth=1, alpha=0.5) + axes[0].fill_between(range(len(error)), error, alpha=0.3) + axes[0].set_xlabel('Sample Index') + axes[0].set_ylabel('Error (Predicted - True)') + axes[0].set_title('Model Output Error vs Ground Truth') + axes[0].legend() + axes[0].grid(True, alpha=0.3) + + # Plot 2: Predictions overlaid on true values + axes[1].plot(y_true_np, label='Ground Truth', linewidth=2, alpha=0.8) + axes[1].plot(y_pred_np, label='Predictions', linewidth=2, alpha=0.8, linestyle='--') + axes[1].set_xlabel('Sample Index') + axes[1].set_ylabel('Value') + axes[1].set_title('Predictions vs Ground Truth') + axes[1].legend() + axes[1].grid(True, alpha=0.3) + + plt.tight_layout() + return fig diff --git a/src/dronecontrol/models/gru_module.py b/src/dronecontrol/models/gru_module.py new file mode 100644 index 0000000..e663471 --- /dev/null +++ b/src/dronecontrol/models/gru_module.py @@ -0,0 +1,62 @@ +"""GRU Lightning module.""" + +from __future__ import annotations + +from typing import Optional, Tuple +import torch +from torch import nn + +from .base_module import BaseModel + + +class GRU(BaseModel): + def __init__( + self, + input_dim: int, + output_dim: int, + hidden_dim: int, + num_layers: int, + scheduler_type: Optional[str] = "ReduceLROnPlateau", + scheduler_kwargs: Optional[dict] = None, + dropout: float = 0.0, + lr: float = 1e-2, + **_, + ): + super().__init__( + input_dim, + output_dim, + hidden_dim, + lr, + num_layers=num_layers, + scheduler_type=scheduler_type, + scheduler_kwargs=scheduler_kwargs + ) + + self.save_hyperparameters({ + "input_dim": input_dim, + "output_dim": output_dim, + "hidden_dim": hidden_dim, + "num_layers": num_layers, + "dropout": dropout, + }) + + self.gru = nn.GRU( + input_size=input_dim, + hidden_size=hidden_dim, + num_layers=num_layers, + bidirectional=False, + batch_first=True, + dropout=dropout if num_layers > 1 else 0.0, + ) + + self.regressor = nn.Linear(hidden_dim, output_dim) + + def forward(self, x: torch.Tensor, hidden: Optional[torch.Tensor] = None) -> Tuple[torch.Tensor, torch.Tensor]: # type: ignore[override] + # Ensure hidden (if provided) is on the same device as x + if hidden is not None: + if hidden.device != x.device: + hidden = hidden.to(x.device) + out, h = self.gru(x, hidden) + else: + out, h = self.gru(x) + return self.regressor(out), h \ No newline at end of file diff --git a/src/models/linear_module.py b/src/dronecontrol/models/linear_module.py similarity index 59% rename from src/models/linear_module.py rename to src/dronecontrol/models/linear_module.py index 2c33b3c..9d29f7f 100644 --- a/src/models/linear_module.py +++ b/src/dronecontrol/models/linear_module.py @@ -4,13 +4,20 @@ import torch from torch import nn +from typing import Optional -from .base_module import BaseLightningModel +from .base_module import BaseModel -class LinearLightningModel(BaseLightningModel): - def __init__(self, input_dim: int, output_dim: int, lr: float = 1e-3, **_: object): - super().__init__(input_dim, output_dim, lr) +class LinearModel(nn.Module): + def __init__( + self, + input_dim: int, + output_dim: int, + **_ + ): + + super().__init__() self.model = nn.Sequential( nn.Linear(input_dim, output_dim) ) diff --git a/src/dronecontrol/models/lstm.py b/src/dronecontrol/models/lstm.py new file mode 100644 index 0000000..cf7b8be --- /dev/null +++ b/src/dronecontrol/models/lstm.py @@ -0,0 +1,57 @@ +"""LSTM Lightning module.""" + +from typing import Tuple +import torch +from torch import nn +from typing import Optional + +from .base_module import BaseModel + + +class LSTM(BaseModel): + def __init__( + self, + input_dim: int, + output_dim: int, + hidden_dim: int, + num_layers: int, + scheduler_type: Optional[str] = "ReduceLROnPlateau", + scheduler_kwargs: Optional[dict] = None, + dropout: float = 0.0, + lr: float = 1e-2, + **_: object, + ): + + super().__init__( + input_dim, + output_dim, + hidden_dim, + lr, + num_layers=num_layers, + scheduler_type=scheduler_type, + scheduler_kwargs=scheduler_kwargs + ) + self.save_hyperparameters() + + self.batch_norm = nn.BatchNorm1d(input_dim) + + self.lstm = nn.LSTM( + input_size=input_dim, + hidden_size=hidden_dim, + num_layers=num_layers, + batch_first=True, + dropout=dropout if num_layers > 1 else 0.0, + ) + self.regressor = nn.Linear(hidden_dim, output_dim) + + def forward(self, x: torch.Tensor, hidden: Optional[torch.Tensor]=None) -> Tuple[torch.Tensor, torch.Tensor]: # type: ignore[override] + # x: [batch, seq_len, input_dim] + + if hidden is not None: + out, h = self.lstm(x, hidden) + else: + out, h = self.lstm(x) + + out = self.regressor(out) + + return out, h \ No newline at end of file diff --git a/src/dronecontrol/models/rnn.py b/src/dronecontrol/models/rnn.py new file mode 100644 index 0000000..c39a160 --- /dev/null +++ b/src/dronecontrol/models/rnn.py @@ -0,0 +1,144 @@ +import torch +from torch import nn +import pytorch_lightning as pl + +class model_nn(pl.LightningModule): + """ + Sequential neural network model (RNN/LSTM/GRU) for aligned sequence-to-sequence mapping U->Y. + """ + def __init__( + self, + input_size: int = 1, + output_size: int = 1, + rnn_type: str = "RNN", # "RNN", "LSTM", "GRU" + hidden_size: int = 16, + num_layers: int = 1, + dropout: float = 0.0, + lr: float = 1e-3, + weight_decay: float = 0.0, + bidirectional: bool = False, + ): + """ + Initialize the model with the specified architecture and hyperparameters. + + Args: + input_size (int): Number of input features. + output_size (int): Number of output features. + rnn_type (str): Type of recurrent layer ("RNN", "LSTM", "GRU"). + hidden_size (int): Number of hidden units in the RNN layer. + num_layers (int): Number of recurrent layers. + dropout (float): Dropout probability between RNN layers. + lr (float): Learning rate for the optimizer. + weight_decay (float): Weight decay (L2 penalty) for the optimizer. + bidirectional (bool): If True, becomes a bidirectional RNN. + """ + super().__init__() + self.save_hyperparameters() + # Select and initialize the RNN type + if rnn_type.upper() == "LSTM": + self.rnn = nn.LSTM( + input_size=input_size, + hidden_size=hidden_size, + num_layers=num_layers, + dropout=dropout if num_layers > 1 else 0.0, + bidirectional=bidirectional, + batch_first=True + ) + elif rnn_type.upper() == "GRU": + self.rnn = nn.GRU( + input_size=input_size, + hidden_size=hidden_size, + num_layers=num_layers, + dropout=dropout if num_layers > 1 else 0.0, + bidirectional=bidirectional, + batch_first=True + ) + elif rnn_type.upper() == "RNN": + self.rnn = nn.RNN( + input_size=input_size, + hidden_size=hidden_size, + num_layers=num_layers, + nonlinearity='tanh', + dropout=dropout if num_layers > 1 else 0.0, + bidirectional=bidirectional, + batch_first=True + ) + else: + raise ValueError("rnn_type must be one of {'RNN','LSTM','GRU'}") + # Linear head for output projection + d = 2 if bidirectional else 1 + self.head = nn.Linear(d * hidden_size, output_size) + self.loss_fn = nn.MSELoss() # Mean squared error loss + self.weight_decay = weight_decay + self.lr = lr + + def forward(self, x: torch.Tensor) -> torch.Tensor: + """ + Forward pass of the model. + + Args: + x (torch.Tensor): Input tensor of shape (batch, T, input_size). + + Returns: + torch.Tensor: Output tensor of shape (batch, T, output_size). + """ + h, _ = self.rnn(x) # h: (batch, T, d*hidden_size) + y = self.head(h) # y: (batch, T, output_size) + return y + + def training_step(self, batch, batch_idx): + """ + Training step: forward pass, loss computation, and logging. + + Args: + batch: Tuple of (input, target) tensors. + batch_idx: Index of the current batch. + + Returns: + torch.Tensor: Computed loss. + """ + x, y = batch + y_hat = self(x) + loss = self.loss_fn(y_hat, y) + self.log("train_loss", loss, on_step=True, on_epoch=True, prog_bar=True) + return loss + + def validation_step(self, batch, batch_idx): + """ + Validation step: forward pass, loss computation, and logging. + + Args: + batch: Tuple of (input, target) tensors. + batch_idx: Index of the current batch. + """ + x, y = batch + y_hat = self(x) + loss = self.loss_fn(y_hat, y) + self.log("val_loss", loss, on_epoch=True, prog_bar=True) + + def test_step(self, batch, batch_idx): + """ + Test step: forward pass, loss computation, and logging. + + Args: + batch: Tuple of (input, target) tensors. + batch_idx: Index of the current batch. + """ + x, y = batch + y_hat = self(x) + loss = self.loss_fn(y_hat, y) + self.log("test_loss", loss, on_epoch=True, prog_bar=True) + + def configure_optimizers(self): + """ + Configure the optimizer for training. + + Returns: + torch.optim.Optimizer: Adam optimizer instance. + """ + opt = torch.optim.Adam( + self.parameters(), + lr=self.lr, + weight_decay=self.weight_decay + ) + return opt diff --git a/src/dronecontrol/models/rnn_module.py b/src/dronecontrol/models/rnn_module.py new file mode 100644 index 0000000..d461829 --- /dev/null +++ b/src/dronecontrol/models/rnn_module.py @@ -0,0 +1,61 @@ +"""Vanilla RNN Lightning module.""" + +from __future__ import annotations +from typing import Optional + +import torch +from torch import nn + +from .base_module import BaseModel + + +class RNN(BaseModel): + def __init__( + self, + input_dim: int, + output_dim: int, + hidden_dim: int, + num_layers: int, + scheduler_type: Optional[str] = "ReduceLROnPlateau", + scheduler_kwargs: Optional[dict] = None, + dropout: float = 0.0, + lr: float = 1e-2, + **_, + ): + + super().__init__( + input_dim, + output_dim, + hidden_dim, + lr, + num_layers=num_layers, + scheduler_type=scheduler_type, + scheduler_kwargs=scheduler_kwargs + ) + + self.save_hyperparameters({ + "hidden_dim": hidden_dim, + "num_layers": num_layers, + "dropout": dropout, + }) + + # Recurrent layers + self.rnn = nn.RNN( + input_size=input_dim, + hidden_size=hidden_dim, + num_layers=num_layers, + batch_first=True, + nonlinearity="tanh", + dropout=dropout if num_layers > 1 else 0.0, + ) + self.regressor = nn.Sequential( + nn.Linear(hidden_dim, output_dim), + ) + + def forward(self, x: torch.Tensor, hidden: Optional[torch.Tensor]) -> tuple[torch.Tensor, torch.Tensor]: # type: ignore[override] + if hidden is not None: + out, h = self.rnn(x, hidden) + else: + out, h = self.rnn(x) + + return self.regressor(out), h \ No newline at end of file diff --git a/src/report_generation.py b/src/dronecontrol/report_generation.py similarity index 98% rename from src/report_generation.py rename to src/dronecontrol/report_generation.py index 071f22f..81d57e0 100644 --- a/src/report_generation.py +++ b/src/dronecontrol/report_generation.py @@ -14,7 +14,7 @@ from reportlab.lib.utils import ImageReader from reportlab.pdfgen import canvas -from .data_loader import load_npz +from dronecontrol.data_process.data_loader import load_npz from .models import MODEL_REGISTRY LOGGER = logging.getLogger(__name__) @@ -125,4 +125,3 @@ def _render_pdf(pdf_path: Path, scenario: str, metrics: Dict[str, Any], figures: y -= img_height * scale + 30 c.save() -```} \ No newline at end of file diff --git a/src/dronecontrol/simulink/__init__.py b/src/dronecontrol/simulink/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/dronecontrol/simulink/engine.py b/src/dronecontrol/simulink/engine.py new file mode 100644 index 0000000..09af7d9 --- /dev/null +++ b/src/dronecontrol/simulink/engine.py @@ -0,0 +1,183 @@ +import matlab.engine +import numpy as np +import matplotlib.pyplot as plt +from typing import Tuple, List, Optional, Dict, Callable + +def colvec(seq: List[float]) -> matlab.single: + """Convert Python sequence to MATLAB column vector (Nx1 double).""" + return matlab.single([[float(v)] for v in seq]) + +def initialize_matlab_engine(pcode_path: str) -> matlab.engine.MatlabEngine: + """ + Start or connect to a MATLAB Engine, add path, and verify model. + Tries to connect to a shared engine first; otherwise starts one with + faster startup options (no desktop/JVM). Then adds the p-code path. + """ + + # Try to connect to an existing shared engine (fast, avoids startup cost) + try: + names = matlab.engine.find_matlab() + except Exception: + names = [] + if names: + eng = matlab.engine.connect_matlab(names[0]) + else: + # Start MATLAB without desktop/JVM to reduce startup time + # Note: adjust options if certain toolboxes require JVM + eng = matlab.engine.start_matlab("-nojvm -nodisplay -nosplash") + if type(eng) is not matlab.engine.MatlabEngine: + raise ValueError("Failed to start MATLAB engine.") + eng.addpath(pcode_path, nargout=0) + + which_output = eng.which('quadcopter_model', '-all') + print("which output:", which_output) + + if which_output == 'quadcopter_model not found.' or 'built-in' in str(which_output).lower(): + eng.quit() + raise ValueError("quadcopter_model not found.") + + # Test model + x_test = colvec([0] * 12) + u_test = colvec([1] * 4) + dxdt_test = eng.feval('quadcopter_model', x_test, u_test, nargout=1) + print("Model test successful. dxdt shape:", np.array(dxdt_test).shape) + + return eng + +def run_discrete_feedback_simulation(eng: matlab.engine.MatlabEngine, + x0: np.ndarray, + controller: Callable[[np.ndarray, float], np.ndarray], + t_end: float, + dt: float = 0.05, + t_start: float = 0.0) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: + """ + Discrete-time simulation with Python feedback: x[k+1] = x[k] + dx[k] * dt, u from controller. + + Args: + eng (MatlabEngine): Initialized engine + x0 (np.ndarray): Initial state (shape (12,)) + controller (Callable): Python function u = controller(x, t) -> np.ndarray (shape (4,)) + t_end (float): Simulation end time + dt (float): Fixed time step (control & integration rate) + t_start (float): Start time + + Returns: + Tuple[np.ndarray, np.ndarray, np.ndarray]: T (steps+1,), X (steps+1, 12), U (steps, 4) + """ + num_steps = int((t_end - t_start) / dt) + T = np.linspace(t_start, t_end, num_steps + 1) + X = np.zeros((num_steps + 1, 12)) + U = np.zeros((num_steps, 4)) + + X[0] = x0 + + for k in range(num_steps): + t = T[k] + x_k = X[k] + + # Python controller computes u from current x and t + u_k = controller(x_k, t) + U[k] = u_k + + # Get dx/dt from MATLAB model + x_mat = colvec(x_k.tolist()) + u_mat = colvec(u_k.tolist()) + dx_mat = eng.quadcopter_model(x_mat, u_mat) + dx_k = np.array(dx_mat).flatten() + + # Euler update: x[k+1] = x[k] + dx[k] * dt + X[k + 1] = x_k + dx_k * dt + + print(f"Discrete simulation complete: {num_steps} steps of dt={dt}s") + return T, X, U + +def plot_discrete_results(T: np.ndarray, X: np.ndarray, U: np.ndarray, + states_to_plot: Optional[List[int]] = None, + labels: Optional[Dict[int, str]] = None, + setpoint: Optional[List[float]] = None) -> None: + """ + Plot states X and controls U vs time. + """ + if states_to_plot is None: + states_to_plot = list(range(6)) # Position & velocity + if labels is None: + labels = {0: 'x', 1: 'y', 2: 'z', 3: 'vx', 4: 'vy', 5: 'vz'} + + fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8)) + fig.suptitle('Discrete-Time Quadcopter Simulation with Python Feedback') + + # Plot states + for idx in states_to_plot: + ax1.plot(T, X[:, idx], label=labels.get(idx, f'State {idx}')) + ax1.set_ylabel('States') + if setpoint: + for i, sp in enumerate(setpoint[:3]): + ax1.axhline(y=sp, color='r', linestyle='--', alpha=0.7, label=f'Setpoint {labels[i]}') + ax1.legend() + ax1.grid(True) + + # Plot controls u (note: U has one less entry than T/X) + T_u = T[:-1] # Align with U steps + for j in range(4): + ax2.plot(T_u, U[:, j], label=f'u{j+1}') + ax2.set_ylabel('Controls u') + ax2.set_xlabel('Time (s)') + ax2.legend() + ax2.grid(True) + + plt.tight_layout() + plt.show() + +def find_hover_thrust(eng: matlab.engine.MatlabEngine, x0: np.ndarray, dt: float, tol: float = 0.001, max_iter: int = 100) -> float: + """ + Binary search to find hover thrust that keeps z at 0. + """ + low = 0.0 + high = 10.0 # Adjust upper bound as needed + for _ in range(max_iter): + mid = (low + high) / 2 + def constant_controller(x, t): + return np.full(4, mid) + + T, X, _ = run_discrete_feedback_simulation(eng, x0, constant_controller, 5.0, dt) # Short sim + z_final = X[-1, 2] + if abs(z_final) < tol: + return mid + elif z_final > 0: + high = mid # Too much thrust, reduce + else: + low = mid # Too little, increase + return (low + high) / 2 + +if __name__ == "__main__": + + pcode_path = r"matlabfiles" # Adjust as needed + x0 = np.zeros(12) + t_end = 10.0 + dt = 0.005 + + eng = initialize_matlab_engine(pcode_path) + + # Find hover thrust + hover_thrust = find_hover_thrust(eng, x0, dt) + print(f"Found hover thrust: {hover_thrust}") + + # Simple proportional controller for z only + zref = 0.0 + k = 0.5 # Proportional gain + + def simple_z_controller(x: np.ndarray, t: float) -> np.ndarray: + z = x[2] + error = zref - z # Error = setpoint - current + u_total = hover_thrust# + k * error + u = np.full(4, u_total) # Equal thrust on all motors + return u # Clip to reasonable bounds + + eng = initialize_matlab_engine("matlabfiles") + + T, X, U = run_discrete_feedback_simulation(eng, x0, simple_z_controller, t_end, dt) + plot_discrete_results(T, X, U, states_to_plot=[0,1,2,3,4,5]) + + eng.quit() + + # Or use function: replace pid_ctrl with lambda x,t: simple_pid_controller(x, t, setpoint=[1,0,3]) diff --git a/src/dronecontrol/simulink/generate_data.py b/src/dronecontrol/simulink/generate_data.py new file mode 100644 index 0000000..398933f --- /dev/null +++ b/src/dronecontrol/simulink/generate_data.py @@ -0,0 +1,189 @@ +from typing import Optional + +from dronecontrol.utils import ensure_dir +from simulator import DroneSimulator, DT +import numpy as np +import logging +import matplotlib.pyplot as plt +from pathlib import Path + + +logger = logging.getLogger(__name__) + + + +def generate_data(sim : DroneSimulator,u_t : list[np.ndarray], x0 : np.ndarray, save_to : Optional[str]) -> tuple[np.ndarray]: + """ + Generate simulation data using the DroneSimulator. + + erxample usage: + u_t = [np.array([0.0, 0.0, 0.0, 9.81])] * 200 # hover input for 200 time steps + x0 = np.zeros(12) # initial state at rest on the ground + history, deriv_history = generate_data(u_t, x0, save_to="simulation_output_path") + """ + assert u_t[0].shape == (4,) and x0.shape == (12,) + + + sim.reset(initial_state=x0) + + logger.info("=== Starting data generation using DroneSimulator ===") + total_time = len(u_t) * DT + logger.info(f"Total simulation time: {total_time:.2f} seconds ({len(u_t)} steps at DT={DT}s)") + + _, trace = sim.rollout(u_t, dt=DT, return_trace=True) # Warm-up to set initial state + + dx_roll = trace["DX"] + X_roll = trace["X"] + + history = np.array(X_roll) + deriv_history = np.array(dx_roll) + # for t in range(len(u_t)): + # st, dxdt = sim.step(u_t[t]) + # history.append(np.array(sim.state)) + # deriv_history.append(np.array(dxdt)) + + + logger.info("=== Data generation completed ===") + if save_to is not None: + logger.info(f"Saving generated data to {save_to}") + ensure_dir(save_to) + np.savetxt(save_to + '/_states.csv', np.array(X_roll), delimiter=',') + np.savetxt(save_to + '/_derivatives.csv', np.array(dx_roll), delimiter=',') + np.savetxt(save_to + '/_inputs.csv', np.array(u_t), delimiter=',') + logger.info("Data saved successfully.") + return history, deriv_history + + +def load_simulation_data(path: str) -> tuple[np.ndarray, np.ndarray, np.ndarray]: + """Load simulation data from CSV files.""" + states = np.loadtxt(path + '/_states.csv', delimiter=',') + derivatives = np.loadtxt(path + '/_derivatives.csv', delimiter=',') + inputs = np.loadtxt(path + '/_inputs.csv', delimiter=',') + return states, derivatives, inputs + + + + + +def plot_run_from_csv(path: str, run_id: int, dt: float = 0.05, show: bool = True, save_path: str | None = None): + """ + Trace les courbes du run spécifié (run_id) à partir des CSV concaténés : + - _states.csv + - _derivatives.csv + - _inputs.csv + + Paramètres + ---------- + path : dossier contenant les fichiers CSV + run_id : identifiant du run à tracer (0, 1, 2, ...) + dt : pas d'échantillonnage en secondes + show : affiche les figures si True + save_path : si non None, enregistre les images dans ce dossier + """ + path = Path(path) + states_path = path / "_states.csv" + derivs_path = path / "_derivatives.csv" + inputs_path = path / "_inputs.csv" + + # --- Lecture CSV --- + states_all = np.loadtxt(states_path, delimiter=",", skiprows=1) + derivs_all = np.loadtxt(derivs_path, delimiter=",", skiprows=1) + inputs_all = np.loadtxt(inputs_path, delimiter=",", skiprows=1) + + # --- Sélection du run --- + states = states_all[states_all[:, 0] == run_id, 1:] # (N+1, 12) + derivs = derivs_all[derivs_all[:, 0] == run_id, 1:] # (N, 12) + inputs = inputs_all[inputs_all[:, 0] == run_id, 1:] # (N, 4) + + if states.size == 0: + raise ValueError(f"Aucun run_id={run_id} trouvé dans {path}") + + N = inputs.shape[0] + t_u = np.arange(N) * dt + t_x = np.arange(N + 1) * dt + t_a = np.arange(N) * dt + + # ================= Fig 1 : Entrées u₁..u₄ ================= + fig_u = plt.figure(figsize=(10, 6)) + for k in range(4): + plt.plot(t_u, inputs[:, k], label=f"u{k+1}") + plt.xlabel("Temps (s)") + plt.ylabel("Commande (u)") + plt.title(f"Run {run_id} — Entrées moteur (u₁..u₄)") + plt.grid(True, alpha=0.3) + plt.legend(loc="best") + + # ================= Fig 2 : Positions x, y, z ================= + fig_pos = plt.figure(figsize=(10, 5)) + plt.plot(t_x, states[:, 0], label="x") + plt.plot(t_x, states[:, 1], label="y") + plt.plot(t_x, states[:, 2], label="z") + plt.xlabel("Temps (s)") + plt.ylabel("Position (m)") + plt.title(f"Run {run_id} — Positions (x, y, z)") + plt.grid(True, alpha=0.3) + plt.legend(loc="best") + + # ================= Fig 3 : Accélérations aₓ, a_y, a_z ================= + fig_acc = plt.figure(figsize=(10, 5)) + plt.plot(t_a, derivs[:, 3], label="aₓ") + plt.plot(t_a, derivs[:, 4], label="a_y") + plt.plot(t_a, derivs[:, 5], label="a_z") + plt.xlabel("Temps (s)") + plt.ylabel("Accélération (m/s²)") + plt.title(f"Run {run_id} — Accélérations (aₓ, a_y, a_z)") + plt.grid(True, alpha=0.3) + plt.legend(loc="best") + + # ================= Fig 4 : Angles θ, φ uniquement ================= + # on convertit rad→deg et wrap dans [-180,180] + angles = np.rad2deg(states[:, 6:8].copy()) # colonnes θ, φ uniquement + angles = (angles + 180.0) % 360.0 - 180.0 + theta = angles[:, 0] # pitch + phi = angles[:, 1] # roll + + # zones "drone à l’envers" + inverted = (np.abs(theta) > 90.0) | (np.abs(phi) > 90.0) + + fig_ang = plt.figure(figsize=(10, 5)) + ax = fig_ang.gca() + ax.plot(t_x, theta, label="θ (pitch)", linewidth=1.8) + ax.plot(t_x, phi, label="φ (roll)", linewidth=1.8, linestyle="--") + ax.set_xlabel("Temps (s)") + ax.set_ylabel("Angle (°)") + ax.set_ylim([-180, 180]) + ax.set_title(f"Run {run_id} — Angles (θ, φ) wrap [-180°, 180°]") + ax.grid(True, alpha=0.3) + ax.legend(loc="best") + + # colorie les zones où le drone est à l’envers + if inverted.any(): + run_on = False + start_idx = 0 + for k, flag in enumerate(inverted): + if flag and not run_on: + run_on = True + start_idx = k + elif run_on and (not flag or k == len(inverted) - 1): + end_idx = k if not flag else k + ax.fill_between(t_x[start_idx:end_idx + 1], -180, 180, alpha=0.12, step="pre") + run_on = False + + # --- Sauvegarde éventuelle --- + if save_path is not None: + save_path = Path(save_path) + save_path.mkdir(parents=True, exist_ok=True) + fig_u.savefig(save_path / f"run{run_id}_inputs.png", dpi=150, bbox_inches="tight") + fig_pos.savefig(save_path / f"run{run_id}_positions.png", dpi=150, bbox_inches="tight") + fig_acc.savefig(save_path / f"run{run_id}_accelerations.png", dpi=150, bbox_inches="tight") + fig_ang.savefig(save_path / f"run{run_id}_angles.png", dpi=150, bbox_inches="tight") + + if show: + plt.show() + + return { + "fig_u": fig_u, + "fig_pos": fig_pos, + "fig_acc": fig_acc, + "fig_ang": fig_ang, + } diff --git a/src/dronecontrol/simulink/main_data_generation.py b/src/dronecontrol/simulink/main_data_generation.py new file mode 100644 index 0000000..a300d8c --- /dev/null +++ b/src/dronecontrol/simulink/main_data_generation.py @@ -0,0 +1,113 @@ +# exécution recommandée : uv run -m dronecontrol.simulink.main_data_generation +import numpy as np +from dronecontrol.simulink.generate_data import * + +from dronecontrol.simulink.simulator import DroneSimulator +from pathlib import Path +from tqdm import tqdm + +# repo_root = .../dronecontrol (car __file__ = .../src/dronecontrol/simulink/main_data_generation.py) +REPO_ROOT = Path(__file__).resolve().parents[3] +OUTDIR = REPO_ROOT / "data" / "control_4_motors" +OUTDIR.mkdir(parents=True, exist_ok=True) + + + +def PRBS(duration, Te, u_min, u_max, t_min=0, t_max=None): + nb_data = int(duration/Te) + if t_max is None: + t_max = duration/5.0 + n_min = int(t_min/Te) + n_max = int(t_max/Te) + sig = np.zeros(nb_data) + c = 0 + while c < nb_data: + amp = np.random.uniform(u_min, u_max) + dur = int(min(np.random.uniform(n_min, n_max), nb_data - c)) + sig[c:c+dur] = amp + c += dur + return sig + + +def main(): + # ----------------- paramètres généraux ----------------- + N_RUNS = 1000 + Te = 0.05 + duration = 100.0 + u_min, u_max = 0.0, 10.0 + t_min = 5 * Te + t_max = 2.0 + x0 = np.zeros(12, dtype=float) + sim = DroneSimulator(initial_state=np.zeros(12)) + + # Dossier de sortie demandé: dronecontrol/data/control_4_motors/ + REPO_ROOT = Path(__file__).resolve().parents[3] + OUTDIR = REPO_ROOT / "data" / "control_4_motors" + OUTDIR.mkdir(parents=True, exist_ok=True) + + # ----------------- accumulateurs ----------------- + all_states = [] # liste de tableaux (Ni+1, 12) par run + all_derivs = [] # liste de tableaux (Ni, 12) par run + all_inputs = [] # liste de tableaux (Ni, 4) par run + all_states_id = [] # run_id aligné sur lignes de states + all_derivs_id = [] # run_id aligné sur lignes de derivs + all_inputs_id = [] # run_id aligné sur lignes d'inputs + + # ----------------- boucle sur les runs ----------------- + for run_id in tqdm(range(N_RUNS)): + # génère un PRBS différent à chaque run + prbs1 = PRBS(duration, Te, u_min, u_max, t_min, t_max) + prbs2 = PRBS(duration, Te, u_min, u_max, t_min, t_max) + prbs3 = PRBS(duration, Te, u_min, u_max, t_min, t_max) + prbs4 = PRBS(duration, Te, u_min, u_max, t_min, t_max) + + U = np.column_stack([prbs1, prbs2, prbs3, prbs4]) # (N,4) + u_t = [U[k, :].astype(float) for k in range(U.shape[0])] + + # on ne sauvegarde pas à chaque run: save_to=None + history, deriv_history = generate_data(sim, u_t, x0, save_to=None) + + states = np.vstack(history) # (N+1, 12) + derivs = np.vstack(deriv_history) # (N, 12) + inputs = U # (N, 4) + + all_states.append(states) + all_derivs.append(derivs) + all_inputs.append(inputs) + + all_states_id.append(np.full((states.shape[0], 1), run_id, dtype=int)) + all_derivs_id.append(np.full((derivs.shape[0], 1), run_id, dtype=int)) + all_inputs_id.append(np.full((inputs.shape[0], 1), run_id, dtype=int)) + + # ----------------- concaténation + sauvegarde ----------------- + # Ajoute une première colonne run_id pour séparer les runs dans un seul CSV + + ST = np.hstack([np.vstack(all_states_id), np.vstack(all_states)]) # ((sum(Ni)+N_RUNS), 1+12) + DV = np.hstack([np.vstack(all_derivs_id), np.vstack(all_derivs)]) # ((sum(Ni)), 1+12) + IN = np.hstack([np.vstack(all_inputs_id), np.vstack(all_inputs)]) # ((sum(Ni)), 1+4) + + # noms de colonnes (en commentaire header) + states_header = "run_id," + ",".join([f"x{i}" for i in range(12)]) + derivs_header = "run_id," + ",".join([f"dx{i}" for i in range(12)]) + inputs_header = "run_id,u1,u2,u3,u4" + + # fichiers de sortie (toujours les mêmes noms) + np.savetxt(OUTDIR / "_states.csv", ST, delimiter=",", fmt="%.10g", header=states_header, comments="") + np.savetxt(OUTDIR / "_derivatives.csv", DV, delimiter=",", fmt="%.10g", header=derivs_header, comments="") + np.savetxt(OUTDIR / "_inputs.csv", IN, delimiter=",", fmt="%.10g", header=inputs_header, comments="") + + print(f"✅ Sauvegardé {N_RUNS} runs concaténés dans : {OUTDIR}") + + +def main2(run_id: int = 0, dt: float = 0.05, save: bool = False): + """ + Charge les CSV concaténés dans OUTDIR et trace uniquement le graphe des angles (θ, φ) pour le run choisi. + """ + save_path = OUTDIR if save else None + print(f"▶️ Plot angles pour run_id={run_id} depuis {OUTDIR} (dt={dt})") + # plot_run_from_csv vient de generate_data.py (importée via *) + plot_run_from_csv(OUTDIR.as_posix(), run_id=run_id, dt=dt, show=True, save_path=save_path) + +if __name__ == "__main__": + # Choisis l’un des deux : + main() # pour générer + enregistrer les 10 runs concaténés diff --git a/src/dronecontrol/simulink/simulator.py b/src/dronecontrol/simulink/simulator.py new file mode 100644 index 0000000..f31e470 --- /dev/null +++ b/src/dronecontrol/simulink/simulator.py @@ -0,0 +1,319 @@ +import matlab +import matplotlib.pyplot as plt +from typing import Optional, Any +import numpy as np +from tqdm import tqdm +import ctypes +import os +from numpy.ctypeslib import ndpointer +import platform +INPUT_FILTER_TAU = 0.2 # time constant for input low-pass filter (seconds) +DT = 0.05 # Default time step for simulation (in seconds) + +DEFAULT_P_CODE_PATH = "matlabfiles" + +import logging +logger = logging.getLogger(__name__) +import time + + +def colvec(seq: list[float]) -> matlab.single: + """Convert Python sequence to MATLAB column vector (Nx1 double).""" + return matlab.single([[float(v)] for v in seq]) + + +class DroneSimulator: + + def __init__(self, initial_state: np.ndarray = np.zeros(12), path_to_pcode: str = DEFAULT_P_CODE_PATH) -> None: + self.eng = None + self.state = np.asarray(initial_state, dtype=np.float64) + self.filtered_input = None + self._dll = None + self._dll_available = False + + # Detect operating system + system_name = platform.system().lower() # 'windows', 'linux', 'darwin' + is_windows = system_name.startswith("win") + is_linux = system_name.startswith("linux") + + try: + # Choose the right extension and folder + lib_folder = os.path.join(path_to_pcode, "codegen", "dll" if is_windows else "lib", "quadcopter_model") + dll_name = "quadcopter_model.dll" if is_windows else "libquadcopter_model.so" + dll_path = os.path.join(lib_folder, dll_name) + + if os.path.isfile(dll_path): + dll = ctypes.CDLL(dll_path) + logger.info(f"Loaded native library: {dll_path}") + + # Optional initialize function + try: + init_fn = getattr(dll, "quadcopter_model_initialize") + init_fn.restype = None + init_fn() + logger.debug("Initialized native library (quadcopter_model_initialize).") + except AttributeError: + pass + + # Define main function prototype + try: + core = getattr(dll, "quadcopter_model") + core.argtypes = [ + ndpointer(dtype=np.float64, ndim=1, flags="C_CONTIGUOUS"), # x[12] + ndpointer(dtype=np.float64, ndim=1, flags="C_CONTIGUOUS"), # u[4] + ndpointer(dtype=np.float64, ndim=1, flags="C_CONTIGUOUS"), # dxdt[12] + ] + core.restype = None + self._dll = dll + self._dll_core = core + self._dll_available = True + logger.info("✅ Native quadcopter model loaded successfully.") + except AttributeError: + logger.warning("⚠️ Core symbol 'quadcopter_model' not found in library.") + self._dll = None + self._dll_available = False + else: + logger.warning(f"⚠️ Native library not found at {dll_path}") + + except Exception as e: + logger.exception(f"❌ Failed to load native library: {e}") + self._dll = None + self._dll_available = False + + # Fallback: use MATLAB engine if no DLL/SO is available + if not self._dll_available: + logger.warning("Native library unavailable, starting MATLAB engine...") + time.sleep(2) + from dronecontrol.simulink.engine import initialize_matlab_engine + self.eng = initialize_matlab_engine(path_to_pcode) + self._has_rk4_step = False + self._has_simulate_rk4 = False + + if self.eng is not None: + try: + exists_rk4 = int(self.eng.feval('exist', 'rk4_step', 'file', nargout=1)) + self._has_rk4_step = exists_rk4 > 0 + except Exception: + pass + try: + exists_seq = int(self.eng.feval('exist', 'simulate_rk4', 'file', nargout=1)) + self._has_simulate_rk4 = exists_seq > 0 + except Exception: + pass + + @property + def pos(self): + return self.state[0:3].astype(np.float64, copy=False) + + @property + def vel(self): + return self.state[3:6].astype(np.float64, copy=False) + + @property + def angles(self): + return self.state[6:9].astype(np.float64, copy=False) + + def accel(self, control_input: np.ndarray | list[float]) -> np.ndarray: + """ Control input is a list of 4 floats: [thrust, tau_phi, tau_theta, tau_psi] wich are the tensions/torques applied by the motors.""" + control = np.asarray(control_input, dtype=float) + _, dxdt = self.step(control) + # Ensure consistent dtype for downstream consumers (float64) + return np.asarray(dxdt[3:6], dtype=np.float64) + + @property + def ang_vel(self): + return self.state[9:12] + + + def reset(self, initial_state: Optional[np.ndarray] = None) -> None: + if initial_state is not None: + self.state = np.asarray(initial_state, dtype=np.float64) + self.filtered_input = None + + def _compute_deriv(self, state: np.ndarray, control_input: np.ndarray) -> np.ndarray: + """Compute dxdt using the native DLL if available, else MATLAB.""" + # Prefer native DLL (fast, no engine round-trip) + if self._dll_available: + x = np.ascontiguousarray(state, dtype=np.float64) + u = np.ascontiguousarray(control_input, dtype=np.float64) + if x.shape[0] != 12: + raise ValueError(f"state must be length 12, got {x.shape}") + if u.shape[0] != 4: + raise ValueError(f"control_input must be length 4, got {u.shape}") + dx = np.empty(12, dtype=np.float64) + # call: void quadcopter_model(const double x[12], const double u[4], double dxdt[12]) + self._dll_core(x, u, dx) # type: ignore[attr-defined] + return dx + + # Fallback to MATLAB engine call + x_matlab = colvec(state.tolist()) + u_matlab = colvec(control_input.tolist()) + dxdt_matlab = self.eng.feval('quadcopter_model', x_matlab, u_matlab, nargout=1) + return np.array(dxdt_matlab, dtype=np.float64).flatten() + + def step(self, control_input: np.ndarray, dt: float = DT) -> tuple[np.ndarray, np.ndarray]: + """Advance the simulation by one time step using RK4 integration.""" + # Apply first-order low-pass filter to the control input (per-channel) + control = np.asarray(control_input, dtype=float) + if self.filtered_input is None: + self.filtered_input = np.zeros_like(control) + tau = INPUT_FILTER_TAU + alpha = dt / (tau + dt) if (tau + dt) != 0 else 1.0 + # exponential smoothing / discrete-time first-order filter + self.filtered_input = self.filtered_input + alpha * (control - self.filtered_input) + voltage = 10*np.tanh(self.filtered_input) + # Prefer MATLAB-side single-call RK4 if available + if not self._dll_available and self._has_rk4_step and self.eng is not None: + x_matlab = colvec(self.state.tolist()) + u_matlab = colvec(voltage.tolist()) + res: Any = self.eng.feval('rk4_step', x_matlab, u_matlab, float(dt), nargout=2) + x_next_mat, dxdt_avg_mat = res + x_next_np = np.array(x_next_mat, dtype=np.float64).flatten() + dxdt_np = np.array(dxdt_avg_mat, dtype=np.float64).flatten() + self.state = x_next_np.astype(np.float64) + return self.state, dxdt_np + elif self._dll_available: + + # Fallback: RK4 via 4 MATLAB calls from Python + k1 = self._compute_deriv(self.state, voltage) + k2 = self._compute_deriv(self.state + 0.5 * dt * k1, voltage) + k3 = self._compute_deriv(self.state + 0.5 * dt * k2, voltage) + k4 = self._compute_deriv(self.state + dt * k3, voltage) + dxdt_avg = (k1 + 2 * k2 + 2 * k3 + k4) / 6.0 + # ensure dtype stability + #self.state = np.asarray(self.state, dtype=np.float64) + self.state += dt * dxdt_avg.astype(np.float64) + return self.state, dxdt_avg.astype(np.float64) + + def rollout(self, control_seq: np.ndarray, dt: float = DT, return_trace: bool = False): + """Run a multi-step simulation efficiently. + control_seq can be shape (T,), (T,1), or (T,4). + Returns (final_state, trace_dict?) depending on return_trace. + """ + U = np.asarray(control_seq, dtype=float) + if U.ndim == 1: + U = U.reshape(-1, 1) + if U.shape[1] == 1: + U = np.repeat(U, 4, axis=1) + + if self._has_simulate_rk4: + # MATLAB-side full rollout with LPF and saturation + x0_mat = colvec(self.state.tolist()) + # Convert to explicit MATLAB numeric matrix to avoid 'like' errors + U_mat = matlab.single(U.tolist()) + if return_trace: + res: Any = self.eng.feval('simulate_rk4', x0_mat, U_mat, float(dt), float(INPUT_FILTER_TAU), nargout=2) + X_mat, DX_mat = res + # Cast to float64 to avoid numpy.float32 leaking upstream + X_np = np.array(X_mat, dtype=np.float64) + DX_np = np.array(DX_mat, dtype=np.float64) + if X_np.ndim == 2 and X_np.shape[0] >= 1: + self.state = X_np[-1].astype(float) + return self.state, {"X": X_np, "DX": DX_np} + else: + # Return only final state to minimize data transfer + xN_mat: Any = self.eng.feval('simulate_rk4_final', x0_mat, U_mat, float(dt), float(INPUT_FILTER_TAU), nargout=1) + self.state = np.array(xN_mat).flatten().astype(float) + return self.state, None + + # Python-side loop fallback using single-step step() (still uses rk4_step if available) + traj = [self.state.copy()] + acc = [] + # Reset LPF for new rollout + self.filtered_input = None + for t in range(U.shape[0]): + _, dxdt = self.step(U[t]) + traj.append(self.state.copy()) + acc.append(dxdt) + X_np = np.vstack(traj).astype(np.float64) + DX_np = np.vstack(acc).astype(np.float64) + if return_trace: + return self.state, {"X": X_np, "DX": DX_np} + return self.state, None + + + +if __name__ == "__main__": + # we validate the correctness of our python bindings + + from pathlib import Path + import yaml + import numpy as np + from dronecontrol.data_process.preparation import prepare_scenario_data + from dronecontrol.data_process.data_loader import AVDataset + import argparse + import cProfile + import pstats + import io + import sys + + def load_config(path: Path): + with path.open("r", encoding="utf-8") as f: + config = yaml.safe_load(f) + if not isinstance(config, dict): + raise ValueError("Configuration file must define a mapping") + config.setdefault("scenarios", []) + config.setdefault("general", {}) + return config # type: ignore[return-value] + + def run_demo(): + cfg_path = Path("config.yaml") + cfg = load_config(cfg_path) + print("Preparing data...") + + dl, _ = prepare_scenario_data(cfg["scenarios"][0], cfg_path.parent) + dl.setup("fit") + data : AVDataset = dl.train_dataset # type: ignore[attr-defined] + fig, axes = plt.subplots(1, 3, figsize=(15, 5)) + sim = DroneSimulator(initial_state=np.zeros(12)) + print("Starting simulation comparison...") + for idx, i in enumerate([0,1,2]): + # Load sample + u : np.ndarray = data[i][0].squeeze().numpy() + a = data[i][1].squeeze().numpy() + + # # 1) Efficient rollout path + # sim.reset() + # _, trace = sim.rollout(u, dt=DT, return_trace=True) + # dx_roll = trace["DX"] + # a_roll = dx_roll[:,5] + + + # 2) Classic per-step path using .step + sim.reset() + a_step = [] + for t in tqdm(range(len(u)), desc=f"sample {i} step sim", leave=True): + _, dxdt = sim.step(np.full((4,), u[t])) # shape (4,) + a_step.append(dxdt[5]) + a_step = np.asarray(a_step) + + # Plot both simulated vs reference + #axes[idx].plot(a_step, label="sim step") + axes[idx].plot(a, label="reference") + axes[idx].plot(a_step, label="sim step") + axes[idx].set_title(f'dxdt for sample {i}') + axes[idx].legend() + plt.show() + + parser = argparse.ArgumentParser(description="Run simulator example (optionally profiled)") + parser.add_argument("--profile", action="store_true", help="Enable cProfile and write stats to file") + parser.add_argument("--profile-out", default="simulator.prof", help="Filename to write profiler stats to") + parser.add_argument("--print-top", type=int, default=50, help="Print top N lines from the profiler by cumulative time") + args = parser.parse_args() + + if args.profile: + pr = cProfile.Profile() + pr.enable() + try: + run_demo() + finally: + pr.disable() + pr.dump_stats(args.profile_out) + s = io.StringIO() + ps = pstats.Stats(pr, stream=s).sort_stats('cumtime') + ps.print_stats(args.print_top) + print(f"Profile written to: {args.profile_out}") + print(s.getvalue()) + else: + run_demo() + \ No newline at end of file diff --git a/src/utils.py b/src/dronecontrol/utils.py similarity index 100% rename from src/utils.py rename to src/dronecontrol/utils.py diff --git a/src/model_training.py b/src/model_training.py deleted file mode 100644 index b1fa734..0000000 --- a/src/model_training.py +++ /dev/null @@ -1,155 +0,0 @@ -"""Model training orchestration using PyTorch Lightning.""" - -from __future__ import annotations - -import logging -from pathlib import Path -from typing import Any, Dict, List, Optional, Tuple - -import pytorch_lightning as pl -import torch -from pytorch_lightning import LightningDataModule -from pytorch_lightning.callbacks import EarlyStopping, ModelCheckpoint -from pytorch_lightning.loggers import CSVLogger -from torch.utils.data import DataLoader, TensorDataset - -from .data_loader import load_npz -from .models import MODEL_REGISTRY - -LOGGER = logging.getLogger(__name__) - -class UAVDataModule(LightningDataModule): - def __init__( - self, - data_path: str, - batch_size: int = 32, - num_workers: int = 0, - ): - super().__init__() - self.data_path = data_path - self.batch_size = batch_size - self.num_workers = num_workers - self.train_ds: Optional[TensorDataset] = None - self.val_ds: Optional[TensorDataset] = None - self.test_ds: Optional[TensorDataset] = None - self.input_dim: Optional[int] = None - self.output_dim: Optional[int] = None - - def setup(self, stage: Optional[str] = None) -> None: # type: ignore[override] - X_train, Y_train, X_val, Y_val, X_test, Y_test = load_npz(self.data_path) - self.input_dim = X_train.shape[-1] - self.output_dim = Y_train.shape[-1] - self.train_ds = TensorDataset(torch.from_numpy(X_train).float(), torch.from_numpy(Y_train).float()) - self.val_ds = TensorDataset(torch.from_numpy(X_val).float(), torch.from_numpy(Y_val).float()) - self.test_ds = TensorDataset(torch.from_numpy(X_test).float(), torch.from_numpy(Y_test).float()) - - def train_dataloader(self) -> DataLoader: # type: ignore[override] - assert self.train_ds is not None - return DataLoader(self.train_ds, batch_size=self.batch_size, shuffle=True, num_workers=self.num_workers) - - def val_dataloader(self) -> DataLoader: # type: ignore[override] - assert self.val_ds is not None - return DataLoader(self.val_ds, batch_size=self.batch_size, shuffle=False, num_workers=self.num_workers) - - def test_dataloader(self) -> DataLoader: # type: ignore[override] - assert self.test_ds is not None - return DataLoader(self.test_ds, batch_size=self.batch_size, shuffle=False, num_workers=self.num_workers) - - -def _determine_accelerator(device: str) -> str: - if device == "auto": - return "gpu" if torch.cuda.is_available() else "cpu" - if device in {"cpu", "gpu"}: - return device - raise ValueError(f"Unsupported device option: {device}") - - -def train_models_for_scenario( - scenario_name: str, - scenario_cfg: Dict[str, Any], - general_cfg: Dict[str, Any], - processed_path: str, -) -> List[Tuple[str, Path]]: - training_cfg = dict(scenario_cfg.get("training", {}) or {}) - models = training_cfg.get("models", []) - if not models: - LOGGER.warning("No models specified for scenario %s", scenario_name) - return [] - - checkpoint_dir = Path(general_cfg.get("checkpoint_dir", "models")) / scenario_name - checkpoint_dir.mkdir(parents=True, exist_ok=True) - - log_dir = Path(general_cfg.get("log_dir", "logs")) / scenario_name - log_dir.mkdir(parents=True, exist_ok=True) - - batch_size = int(training_cfg.get("batch_size", 32)) - epochs = int(training_cfg.get("epochs", 50)) - lr = float(training_cfg.get("lr", 1e-3)) - num_workers = int(general_cfg.get("num_workers", 0)) - resume = bool(general_cfg.get("resume_from_checkpoint", False)) - model_params: Dict[str, Any] = dict(training_cfg.get("model_params", {}) or {}) - - datamodule = UAVDataModule(processed_path, batch_size=batch_size, num_workers=num_workers) - datamodule.setup() - - checkpoints: List[Tuple[str, Path]] = [] - accelerator = _determine_accelerator(str(general_cfg.get("device", "auto"))) - - for model_name in models: - model_name = model_name.lower() - model_cls = MODEL_REGISTRY.get(model_name) - if model_cls is None: - LOGGER.error("Unknown model type %s", model_name) - continue - assert datamodule.input_dim is not None and datamodule.output_dim is not None - extra_kwargs = dict(model_params.get(model_name, {})) - model = model_cls(datamodule.input_dim, datamodule.output_dim, lr=lr, **extra_kwargs) - - callbacks = [ - EarlyStopping(monitor="val_loss", patience=10, mode="min"), - ModelCheckpoint( - dirpath=checkpoint_dir / model_name, - filename="{epoch:03d}-{val_loss:.4f}", - save_top_k=1, - monitor="val_loss", - mode="min", - ), - ] - csv_logger = CSVLogger(save_dir=log_dir, name=model_name) - - trainer = pl.Trainer( - max_epochs=epochs, - accelerator=accelerator, - devices=1, - callbacks=callbacks, - logger=csv_logger, - deterministic=bool(general_cfg.get("deterministic", True)), - enable_checkpointing=True, - log_every_n_steps=10, - ) - - ckpt_path = None - if resume: - last_ckpt = _find_last_checkpoint(checkpoint_dir / model_name) - if last_ckpt: - ckpt_path = str(last_ckpt) - LOGGER.info("Resuming %s from %s", model_name, ckpt_path) - - trainer.fit(model, datamodule=datamodule, ckpt_path=ckpt_path) - trainer.validate(model, datamodule=datamodule) - - best_ckpt = callbacks[1].best_model_path # ModelCheckpoint - if best_ckpt: - checkpoints.append((model_name, Path(best_ckpt))) - LOGGER.info("Best checkpoint for %s stored at %s", model_name, best_ckpt) - else: - LOGGER.warning("Trainer did not produce a checkpoint for %s", model_name) - - return checkpoints - - -def _find_last_checkpoint(directory: Path) -> Optional[Path]: - if not directory.exists(): - return None - checkpoints = sorted(directory.glob("*.ckpt"), key=lambda p: p.stat().st_mtime, reverse=True) - return checkpoints[0] if checkpoints else None diff --git a/src/models/__init__.py b/src/models/__init__.py deleted file mode 100644 index 9157522..0000000 --- a/src/models/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -"""Model registry for UAV control.""" - -from .linear_module import LinearLightningModel -from .rnn_module import RNNLightningModel -from .gru_module import GRULightningModel - -MODEL_REGISTRY = { - "linear": LinearLightningModel, - "rnn": RNNLightningModel, - "gru": GRULightningModel, -} - -__all__ = [ - "LinearLightningModel", - "RNNLightningModel", - "GRULightningModel", - "MODEL_REGISTRY", -] diff --git a/src/models/base_module.py b/src/models/base_module.py deleted file mode 100644 index 79b5001..0000000 --- a/src/models/base_module.py +++ /dev/null @@ -1,33 +0,0 @@ -"""Base Lightning module definitions.""" - -from __future__ import annotations - -import pytorch_lightning as pl -import torch -from torch import nn - - -class BaseLightningModel(pl.LightningModule): - def __init__(self, input_dim: int, output_dim: int, lr: float = 1e-3): - super().__init__() - self.save_hyperparameters() - - def forward(self, x: torch.Tensor) -> torch.Tensor: # pragma: no cover - abstract - raise NotImplementedError - - def training_step(self, batch, batch_idx): # type: ignore[override] - x, y = batch - y_hat = self(x) - loss = nn.functional.mse_loss(y_hat, y) - self.log("train_loss", loss, prog_bar=True) - return loss - - def validation_step(self, batch, batch_idx): # type: ignore[override] - x, y = batch - y_hat = self(x) - loss = nn.functional.mse_loss(y_hat, y) - self.log("val_loss", loss, prog_bar=True) - return loss - - def configure_optimizers(self): # type: ignore[override] - return torch.optim.Adam(self.parameters(), lr=self.hparams.lr) diff --git a/src/models/gru_module.py b/src/models/gru_module.py deleted file mode 100644 index 69e8c45..0000000 --- a/src/models/gru_module.py +++ /dev/null @@ -1,46 +0,0 @@ -"""GRU Lightning module.""" - -from __future__ import annotations - -import torch -from torch import nn - -from .base_module import BaseLightningModel - - -class GRULightningModel(BaseLightningModel): - def __init__( - self, - input_dim: int, - output_dim: int, - lr: float = 1e-3, - hidden_dim: int = 64, - num_layers: int = 1, - dropout: float = 0.0, - **_: object, - ): - super().__init__(input_dim, output_dim, lr) - self.save_hyperparameters({ - "hidden_dim": hidden_dim, - "num_layers": num_layers, - "dropout": dropout, - }) - self.gru = nn.GRU( - input_size=input_dim, - hidden_size=hidden_dim, - num_layers=num_layers, - batch_first=True, - dropout=dropout if num_layers > 1 else 0.0, - ) - self.regressor = nn.Sequential( - nn.Linear(hidden_dim, hidden_dim // 2 if hidden_dim > 1 else 1), - nn.ReLU(), - nn.Linear(hidden_dim // 2 if hidden_dim > 1 else 1, output_dim), - ) - - def forward(self, x: torch.Tensor) -> torch.Tensor: # type: ignore[override] - if x.ndim == 2: - x = x.unsqueeze(1) - _, hidden = self.gru(x) - last_hidden = hidden[-1] - return self.regressor(last_hidden) diff --git a/src/models/rnn_module.py b/src/models/rnn_module.py deleted file mode 100644 index 6558c7d..0000000 --- a/src/models/rnn_module.py +++ /dev/null @@ -1,42 +0,0 @@ -"""Vanilla RNN Lightning module.""" - -from __future__ import annotations - -import torch -from torch import nn - -from .base_module import BaseLightningModel - - -class RNNLightningModel(BaseLightningModel): - def __init__( - self, - input_dim: int, - output_dim: int, - lr: float = 1e-3, - hidden_dim: int = 64, - num_layers: int = 1, - dropout: float = 0.0, - **_: object, - ): - super().__init__(input_dim, output_dim, lr) - self.save_hyperparameters({ - "hidden_dim": hidden_dim, - "num_layers": num_layers, - "dropout": dropout, - }) - self.rnn = nn.RNN( - input_size=input_dim, - hidden_size=hidden_dim, - num_layers=num_layers, - batch_first=True, - dropout=dropout if num_layers > 1 else 0.0, - ) - self.regressor = nn.Linear(hidden_dim, output_dim) - - def forward(self, x: torch.Tensor) -> torch.Tensor: # type: ignore[override] - if x.ndim == 2: - x = x.unsqueeze(1) - _, hidden = self.rnn(x) - last_hidden = hidden[-1] - return self.regressor(last_hidden) diff --git a/tests/commande/test_simple_optim.py b/tests/commande/test_simple_optim.py new file mode 100644 index 0000000..779ec62 --- /dev/null +++ b/tests/commande/test_simple_optim.py @@ -0,0 +1,179 @@ + +import torch +import pytest +from dronecontrol.commande.MPC_cvxpy import SimpleOptimizer + + +def simple_quadratic_loss(u, x_ref): + return torch.mean((u - x_ref) ** 2) + + +def test_optimizer_initialization(): + optimizer = SimpleOptimizer( + trajectory_loss_fn=simple_quadratic_loss, + lr=1.0, + max_iter=5, + history_size=5, + max_epochs=2, + ) + + assert callable(optimizer.trajectory_loss_fn) + assert optimizer.lr == 1.0 + assert optimizer.max_iter == 5 + assert optimizer.history_size == 5 + assert optimizer.max_epochs == 2 + + +def test_optimization_converges_quadratic(): + # small problem so LBFGS converges quickly + target = torch.randn(8, 1) + u_init = torch.zeros_like(target) + + optimizer = SimpleOptimizer( + trajectory_loss_fn=simple_quadratic_loss, + lr=1.0, + max_iter=10, + history_size=5, + max_epochs=8, + ) + + u_opt = optimizer.optimize(u_init, target, verbose=False) + + initial_loss = simple_quadratic_loss(u_init, target).item() + final_loss = simple_quadratic_loss(u_opt, target).item() + + assert final_loss < initial_loss + assert u_opt.shape == target.shape + + +def test_get_tensions_bounds_and_values(): + tensions = torch.tensor([[0.0], [2.0], [-2.0]]) + u_min, u_max = -1.0, 1.0 + + out = SimpleOptimizer.get_tensions(tensions, u_min, u_max) + assert torch.all(out >= u_min - 1e-6) + assert torch.all(out <= u_max + 1e-6) + + # check monotonicity + assert out[1] > out[0] > out[2] + + +def test_optimization_with_constraints_improves_loss(): + target = torch.tensor([[0.2], [0.4], [-0.3]]) + u_init = torch.zeros_like(target) + u_min, u_max = -1.0, 1.0 + + def constrained_loss(u, x_ref): + u_actual = SimpleOptimizer.get_tensions(u, u_min, u_max) + return torch.mean((u_actual - x_ref) ** 2) + + optimizer = SimpleOptimizer( + trajectory_loss_fn=constrained_loss, + lr=0.5, + max_iter=8, + history_size=4, + max_epochs=8, + ) + + u_opt_tensions = optimizer.optimize(u_init, target, verbose=False) + final_loss = constrained_loss(u_opt_tensions, target).item() + initial_loss = constrained_loss(u_init, target).item() + assert final_loss < initial_loss + + +def test_zero_max_epochs_returns_initial(): + target = torch.randn(4, 1) + u_init = torch.zeros_like(target) + + optimizer = SimpleOptimizer( + trajectory_loss_fn=simple_quadratic_loss, + lr=1.0, + max_iter=5, + history_size=3, + max_epochs=0, + ) + + result = optimizer.optimize(u_init, target, verbose=False) + # if no epochs, optimizer should return u_init (detached) + assert torch.allclose(result, u_init) + + +def test_verbose_prints(capsys): + target = torch.randn(6, 1) + u_init = torch.zeros_like(target) + + optimizer = SimpleOptimizer( + trajectory_loss_fn=simple_quadratic_loss, + lr=1.0, + max_iter=3, + history_size=3, + max_epochs=5, + ) + + optimizer.optimize(u_init, target, verbose=True) + captured = capsys.readouterr() + assert "Optimization completed" in captured.out + + +def test_hover_control_constant_due_to_gravity(): + """Use the existing TrajectoryLoss: when x_ref == x0 the optimizer + should produce a nearly-constant thrust (due to gravity offset). + """ + import torch.nn as nn + from dronecontrol.commande.loss import TrajectoryLoss + + g = 9.81 + dt = 0.1 + horizon = 20 + + x0_pos = 1.0 + x0_vel = 0.0 + + # reference equal to initial position across horizon + x_ref = torch.full((horizon, 1), x0_pos) + + # initial control guess + u_init = torch.zeros_like(x_ref) + + # accel_model maps control u to acceleration; include gravity offset + class AccelModel(nn.Module): + def __init__(self, g_val: float): + super().__init__() + self.g = g_val + + def forward(self, u_tensor: torch.Tensor) -> torch.Tensor: + return u_tensor - self.g + + accel_model = AccelModel(g) + + # simple quadratic Q and R (identity) + Q = torch.eye(horizon) + R = torch.eye(horizon) + + loss_module = TrajectoryLoss(accel_model=accel_model, horizon=horizon, dt=dt, Q_tensor=Q, R_tensor=R) + + # wrapper to match SimpleOptimizer signature (u, x_ref) + def traj_loss_wrapper(u, x_ref_local): + # TrajectoryLoss expects (u, x_ref, v0, x0) + return loss_module(u, x_ref_local, torch.tensor([x0_vel]), torch.tensor([x0_pos])) + + optimizer = SimpleOptimizer( + trajectory_loss_fn=traj_loss_wrapper, + lr=1.0, + max_iter=40, + history_size=10, + max_epochs=80, + ) + + u_opt = optimizer.optimize(u_init, x_ref, verbose=False) + + u_opt_np = u_opt.detach().cpu().numpy().flatten() + mean_u = float(u_opt_np.mean()) + std_u = float(u_opt_np.std()) + + # mean should be close to g (thrust balancing gravity) and fairly constant + assert abs(mean_u - g) < 1.0, f"mean thrust {mean_u} not close to g={g}" + assert std_u < 1.0, f"thrust not constant enough (std={std_u})" + +if __name__ == '__main__': + pytest.main([__file__]) diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..c9b7cf0 --- /dev/null +++ b/uv.lock @@ -0,0 +1,1444 @@ +version = 1 +revision = 3 +requires-python = "==3.11.*" + +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" }, +] + +[[package]] +name = "aiohttp" +version = "3.13.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1c/ce/3b83ebba6b3207a7135e5fcaba49706f8a4b6008153b4e30540c982fae26/aiohttp-3.13.2.tar.gz", hash = "sha256:40176a52c186aefef6eb3cad2cdd30cd06e3afbe88fe8ab2af9c0b90f228daca", size = 7837994, upload-time = "2025-10-28T20:59:39.937Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/74/b321e7d7ca762638cdf8cdeceb39755d9c745aff7a64c8789be96ddf6e96/aiohttp-3.13.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4647d02df098f6434bafd7f32ad14942f05a9caa06c7016fdcc816f343997dd0", size = 743409, upload-time = "2025-10-28T20:56:00.354Z" }, + { url = "https://files.pythonhosted.org/packages/99/3d/91524b905ec473beaf35158d17f82ef5a38033e5809fe8742e3657cdbb97/aiohttp-3.13.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e3403f24bcb9c3b29113611c3c16a2a447c3953ecf86b79775e7be06f7ae7ccb", size = 497006, upload-time = "2025-10-28T20:56:01.85Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d3/7f68bc02a67716fe80f063e19adbd80a642e30682ce74071269e17d2dba1/aiohttp-3.13.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:43dff14e35aba17e3d6d5ba628858fb8cb51e30f44724a2d2f0c75be492c55e9", size = 493195, upload-time = "2025-10-28T20:56:03.314Z" }, + { url = "https://files.pythonhosted.org/packages/98/31/913f774a4708775433b7375c4f867d58ba58ead833af96c8af3621a0d243/aiohttp-3.13.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e2a9ea08e8c58bb17655630198833109227dea914cd20be660f52215f6de5613", size = 1747759, upload-time = "2025-10-28T20:56:04.904Z" }, + { url = "https://files.pythonhosted.org/packages/e8/63/04efe156f4326f31c7c4a97144f82132c3bb21859b7bb84748d452ccc17c/aiohttp-3.13.2-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53b07472f235eb80e826ad038c9d106c2f653584753f3ddab907c83f49eedead", size = 1704456, upload-time = "2025-10-28T20:56:06.986Z" }, + { url = "https://files.pythonhosted.org/packages/8e/02/4e16154d8e0a9cf4ae76f692941fd52543bbb148f02f098ca73cab9b1c1b/aiohttp-3.13.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e736c93e9c274fce6419af4aac199984d866e55f8a4cec9114671d0ea9688780", size = 1807572, upload-time = "2025-10-28T20:56:08.558Z" }, + { url = "https://files.pythonhosted.org/packages/34/58/b0583defb38689e7f06798f0285b1ffb3a6fb371f38363ce5fd772112724/aiohttp-3.13.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ff5e771f5dcbc81c64898c597a434f7682f2259e0cd666932a913d53d1341d1a", size = 1895954, upload-time = "2025-10-28T20:56:10.545Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f3/083907ee3437425b4e376aa58b2c915eb1a33703ec0dc30040f7ae3368c6/aiohttp-3.13.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3b6fb0c207cc661fa0bf8c66d8d9b657331ccc814f4719468af61034b478592", size = 1747092, upload-time = "2025-10-28T20:56:12.118Z" }, + { url = "https://files.pythonhosted.org/packages/ac/61/98a47319b4e425cc134e05e5f3fc512bf9a04bf65aafd9fdcda5d57ec693/aiohttp-3.13.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:97a0895a8e840ab3520e2288db7cace3a1981300d48babeb50e7425609e2e0ab", size = 1606815, upload-time = "2025-10-28T20:56:14.191Z" }, + { url = "https://files.pythonhosted.org/packages/97/4b/e78b854d82f66bb974189135d31fce265dee0f5344f64dd0d345158a5973/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9e8f8afb552297aca127c90cb840e9a1d4bfd6a10d7d8f2d9176e1acc69bad30", size = 1723789, upload-time = "2025-10-28T20:56:16.101Z" }, + { url = "https://files.pythonhosted.org/packages/ed/fc/9d2ccc794fc9b9acd1379d625c3a8c64a45508b5091c546dea273a41929e/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ed2f9c7216e53c3df02264f25d824b079cc5914f9e2deba94155190ef648ee40", size = 1718104, upload-time = "2025-10-28T20:56:17.655Z" }, + { url = "https://files.pythonhosted.org/packages/66/65/34564b8765ea5c7d79d23c9113135d1dd3609173da13084830f1507d56cf/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:99c5280a329d5fa18ef30fd10c793a190d996567667908bef8a7f81f8202b948", size = 1785584, upload-time = "2025-10-28T20:56:19.238Z" }, + { url = "https://files.pythonhosted.org/packages/30/be/f6a7a426e02fc82781afd62016417b3948e2207426d90a0e478790d1c8a4/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2ca6ffef405fc9c09a746cb5d019c1672cd7f402542e379afc66b370833170cf", size = 1595126, upload-time = "2025-10-28T20:56:20.836Z" }, + { url = "https://files.pythonhosted.org/packages/e5/c7/8e22d5d28f94f67d2af496f14a83b3c155d915d1fe53d94b66d425ec5b42/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:47f438b1a28e926c37632bff3c44df7d27c9b57aaf4e34b1def3c07111fdb782", size = 1800665, upload-time = "2025-10-28T20:56:22.922Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/91133c8b68b1da9fc16555706aa7276fdf781ae2bb0876c838dd86b8116e/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9acda8604a57bb60544e4646a4615c1866ee6c04a8edef9b8ee6fd1d8fa2ddc8", size = 1739532, upload-time = "2025-10-28T20:56:25.924Z" }, + { url = "https://files.pythonhosted.org/packages/17/6b/3747644d26a998774b21a616016620293ddefa4d63af6286f389aedac844/aiohttp-3.13.2-cp311-cp311-win32.whl", hash = "sha256:868e195e39b24aaa930b063c08bb0c17924899c16c672a28a65afded9c46c6ec", size = 431876, upload-time = "2025-10-28T20:56:27.524Z" }, + { url = "https://files.pythonhosted.org/packages/c3/63/688462108c1a00eb9f05765331c107f95ae86f6b197b865d29e930b7e462/aiohttp-3.13.2-cp311-cp311-win_amd64.whl", hash = "sha256:7fd19df530c292542636c2a9a85854fab93474396a52f1695e799186bbd7f24c", size = 456205, upload-time = "2025-10-28T20:56:29.062Z" }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, +] + +[[package]] +name = "appnope" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/35/5d/752690df9ef5b76e169e68d6a129fa6d08a7100ca7f754c89495db3c6019/appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee", size = 4170, upload-time = "2024-02-06T09:43:11.258Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321, upload-time = "2024-02-06T09:43:09.663Z" }, +] + +[[package]] +name = "asttokens" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4a/e7/82da0a03e7ba5141f05cce0d302e6eed121ae055e0456ca228bf693984bc/asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7", size = 61978, upload-time = "2024-11-30T04:30:14.439Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2", size = 26918, upload-time = "2024-11-30T04:30:10.946Z" }, +] + +[[package]] +name = "attrs" +version = "25.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, + { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, + { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, + { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, + { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, + { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, + { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, + { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, + { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, + { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" }, + { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" }, + { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" }, + { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" }, + { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" }, + { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" }, + { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" }, + { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" }, + { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" }, + { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" }, + { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" }, + { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" }, + { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" }, + { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" }, + { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" }, + { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, +] + +[[package]] +name = "clarabel" +version = "0.11.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, + { name = "numpy" }, + { name = "scipy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/81/e2/47f692161779dbd98876015de934943effb667a014e6f79a6d746b3e4c2a/clarabel-0.11.1.tar.gz", hash = "sha256:e7c41c47f0e59aeab99aefff9e58af4a8753ee5269bbeecbd5526fc6f41b9598", size = 253949, upload-time = "2025-06-11T16:49:05.864Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/f7/f82698b6d00a40a80c67e9a32b2628886aadfaf7f7b32daa12a463e44571/clarabel-0.11.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:c39160e4222040f051f2a0598691c4f9126b4d17f5b9e7678f76c71d611e12d8", size = 1039511, upload-time = "2025-06-11T16:48:58.525Z" }, + { url = "https://files.pythonhosted.org/packages/b0/8f/13650cfe25762b51175c677330e6471d5d2c5851a6fbd6df77f0681bb34e/clarabel-0.11.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:8963687ee250d27310d139eea5a6816f9c3ae31f33691b56579ca4f0f0b64b63", size = 935135, upload-time = "2025-06-11T16:48:59.901Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9e/7af10d2b540b39f1a05d1ebba604fce933cc9bc0e65e88ec3b7a84976425/clarabel-0.11.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4837b9d0db01e98239f04b1e3526a6cf568529d3c19a8b3f591befdc467f9bb", size = 1079226, upload-time = "2025-06-11T16:49:00.987Z" }, + { url = "https://files.pythonhosted.org/packages/6b/a9/c76edf781ca3283186ff4b54a9a4fb51367fd04313a68e2b09f062407439/clarabel-0.11.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8c41aaa6f3f8c0f3bd9d86c3e568dcaee079562c075bd2ec9fb3a80287380ef", size = 1164345, upload-time = "2025-06-11T16:49:02.675Z" }, + { url = "https://files.pythonhosted.org/packages/41/e6/4eee3062088c221e5a18b054e51c69f616e0bb0dc1b0a1a5e0fe90dfa18e/clarabel-0.11.1-cp39-abi3-win_amd64.whl", hash = "sha256:557d5148a4377ae1980b65d00605ae870a8f34f95f0f6a41e04aa6d3edf67148", size = 887310, upload-time = "2025-06-11T16:49:04.277Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "comm" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/13/7d740c5849255756bc17888787313b61fd38a0a8304fc4f073dfc46122aa/comm-0.2.3.tar.gz", hash = "sha256:2dc8048c10962d55d7ad693be1e7045d891b7ce8d999c97963a5e3e99c055971", size = 6319, upload-time = "2025-07-25T14:02:04.452Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl", hash = "sha256:c615d91d75f7f04f095b30d1c1711babd43bdc6419c1be9886a85f2f4e489417", size = 7294, upload-time = "2025-07-25T14:02:02.896Z" }, +] + +[[package]] +name = "contourpy" +version = "1.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/2e/c4390a31919d8a78b90e8ecf87cd4b4c4f05a5b48d05ec17db8e5404c6f4/contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1", size = 288773, upload-time = "2025-07-26T12:01:02.277Z" }, + { url = "https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381", size = 270149, upload-time = "2025-07-26T12:01:04.072Z" }, + { url = "https://files.pythonhosted.org/packages/30/2e/dd4ced42fefac8470661d7cb7e264808425e6c5d56d175291e93890cce09/contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7", size = 329222, upload-time = "2025-07-26T12:01:05.688Z" }, + { url = "https://files.pythonhosted.org/packages/f2/74/cc6ec2548e3d276c71389ea4802a774b7aa3558223b7bade3f25787fafc2/contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1", size = 377234, upload-time = "2025-07-26T12:01:07.054Z" }, + { url = "https://files.pythonhosted.org/packages/03/b3/64ef723029f917410f75c09da54254c5f9ea90ef89b143ccadb09df14c15/contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a", size = 380555, upload-time = "2025-07-26T12:01:08.801Z" }, + { url = "https://files.pythonhosted.org/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db", size = 355238, upload-time = "2025-07-26T12:01:10.319Z" }, + { url = "https://files.pythonhosted.org/packages/98/56/f914f0dd678480708a04cfd2206e7c382533249bc5001eb9f58aa693e200/contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620", size = 1326218, upload-time = "2025-07-26T12:01:12.659Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d7/4a972334a0c971acd5172389671113ae82aa7527073980c38d5868ff1161/contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f", size = 1392867, upload-time = "2025-07-26T12:01:15.533Z" }, + { url = "https://files.pythonhosted.org/packages/75/3e/f2cc6cd56dc8cff46b1a56232eabc6feea52720083ea71ab15523daab796/contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff", size = 183677, upload-time = "2025-07-26T12:01:17.088Z" }, + { url = "https://files.pythonhosted.org/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42", size = 225234, upload-time = "2025-07-26T12:01:18.256Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b6/71771e02c2e004450c12b1120a5f488cad2e4d5b590b1af8bad060360fe4/contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470", size = 193123, upload-time = "2025-07-26T12:01:19.848Z" }, + { url = "https://files.pythonhosted.org/packages/a5/29/8dcfe16f0107943fa92388c23f6e05cff0ba58058c4c95b00280d4c75a14/contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497", size = 278809, upload-time = "2025-07-26T12:02:52.74Z" }, + { url = "https://files.pythonhosted.org/packages/85/a9/8b37ef4f7dafeb335daee3c8254645ef5725be4d9c6aa70b50ec46ef2f7e/contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8", size = 261593, upload-time = "2025-07-26T12:02:54.037Z" }, + { url = "https://files.pythonhosted.org/packages/0a/59/ebfb8c677c75605cc27f7122c90313fd2f375ff3c8d19a1694bda74aaa63/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e", size = 302202, upload-time = "2025-07-26T12:02:55.947Z" }, + { url = "https://files.pythonhosted.org/packages/3c/37/21972a15834d90bfbfb009b9d004779bd5a07a0ec0234e5ba8f64d5736f4/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989", size = 329207, upload-time = "2025-07-26T12:02:57.468Z" }, + { url = "https://files.pythonhosted.org/packages/0c/58/bd257695f39d05594ca4ad60df5bcb7e32247f9951fd09a9b8edb82d1daa/contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77", size = 225315, upload-time = "2025-07-26T12:02:58.801Z" }, +] + +[[package]] +name = "cvxpy" +version = "1.7.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "clarabel" }, + { name = "numpy" }, + { name = "osqp" }, + { name = "scipy" }, + { name = "scs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e1/cf/583d8c25bf1ec8d43e0f9953fa3d48f095022dc2fc7e7a437ebdeaf16d9f/cvxpy-1.7.3.tar.gz", hash = "sha256:241d364f5962a1d68c4ae8393480766a09326e5771e2286d33a948e1976cbe70", size = 1635660, upload-time = "2025-09-22T18:21:42.245Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/9e/4149d07f6448c1fb805ea9d94e417d5f5f80d278f6a969ab8ce24a38ae25/cvxpy-1.7.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:abe973a4a03c7beb775b017786f6cd9b3138f0d0c0f01b3a49b75b3314c0e7d8", size = 1530493, upload-time = "2025-09-22T18:08:03.61Z" }, + { url = "https://files.pythonhosted.org/packages/89/f4/38b5934af332ab38c19d8e9d991cc41332938f90813404405873fa18a87e/cvxpy-1.7.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f151096879123ae9a8957468026a20748d29c0d4b72c88418a2a913bb743d207", size = 1189317, upload-time = "2025-09-22T18:08:04.759Z" }, + { url = "https://files.pythonhosted.org/packages/35/65/de046bc747c9150d42b8ef04e3ff500d80e2fa05561ad7cc390ea576420f/cvxpy-1.7.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44c021a3732092f687e7d693524e4cf90aff449b40eeeb1c4e871fac0b83c2f4", size = 1202085, upload-time = "2025-09-22T18:09:35.239Z" }, + { url = "https://files.pythonhosted.org/packages/86/d7/d912505a6230995ddf31badb97a91b60d489ee1e7585edb3718b40fea703/cvxpy-1.7.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7743b261b92e12aef5a7ed9593314e4ceb6cba2c897b21adab70ef02d2ca54c", size = 1231440, upload-time = "2025-09-22T18:09:36.466Z" }, + { url = "https://files.pythonhosted.org/packages/f9/2a/27d8819e288ed3e86ba3f927d774d620fd4d91ea5a8fca463cbdcfc2af82/cvxpy-1.7.3-cp311-cp311-win_amd64.whl", hash = "sha256:e1ba8a446adab630aaa5389acfe4c59a44f0ab3ebff34758097fb4ee383db592", size = 1130136, upload-time = "2025-09-22T18:02:06.632Z" }, +] + +[[package]] +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, +] + +[[package]] +name = "debugpy" +version = "1.8.17" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/ad/71e708ff4ca377c4230530d6a7aa7992592648c122a2cd2b321cf8b35a76/debugpy-1.8.17.tar.gz", hash = "sha256:fd723b47a8c08892b1a16b2c6239a8b96637c62a59b94bb5dab4bac592a58a8e", size = 1644129, upload-time = "2025-09-17T16:33:20.633Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/53/3af72b5c159278c4a0cf4cffa518675a0e73bdb7d1cac0239b815502d2ce/debugpy-1.8.17-cp311-cp311-macosx_15_0_universal2.whl", hash = "sha256:d3fce3f0e3de262a3b67e69916d001f3e767661c6e1ee42553009d445d1cd840", size = 2207154, upload-time = "2025-09-17T16:33:29.457Z" }, + { url = "https://files.pythonhosted.org/packages/8f/6d/204f407df45600e2245b4a39860ed4ba32552330a0b3f5f160ae4cc30072/debugpy-1.8.17-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:c6bdf134457ae0cac6fb68205776be635d31174eeac9541e1d0c062165c6461f", size = 3170322, upload-time = "2025-09-17T16:33:30.837Z" }, + { url = "https://files.pythonhosted.org/packages/f2/13/1b8f87d39cf83c6b713de2620c31205299e6065622e7dd37aff4808dd410/debugpy-1.8.17-cp311-cp311-win32.whl", hash = "sha256:e79a195f9e059edfe5d8bf6f3749b2599452d3e9380484cd261f6b7cd2c7c4da", size = 5155078, upload-time = "2025-09-17T16:33:33.331Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c5/c012c60a2922cc91caa9675d0ddfbb14ba59e1e36228355f41cab6483469/debugpy-1.8.17-cp311-cp311-win_amd64.whl", hash = "sha256:b532282ad4eca958b1b2d7dbcb2b7218e02cb934165859b918e3b6ba7772d3f4", size = 5179011, upload-time = "2025-09-17T16:33:35.711Z" }, + { url = "https://files.pythonhosted.org/packages/b0/d0/89247ec250369fc76db477720a26b2fce7ba079ff1380e4ab4529d2fe233/debugpy-1.8.17-py2.py3-none-any.whl", hash = "sha256:60c7dca6571efe660ccb7a9508d73ca14b8796c4ed484c2002abba714226cfef", size = 5283210, upload-time = "2025-09-17T16:34:25.835Z" }, +] + +[[package]] +name = "decorator" +version = "5.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711, upload-time = "2025-02-24T04:41:34.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, +] + +[[package]] +name = "dronecontrol" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "cvxpy" }, + { name = "filterpy" }, + { name = "ipykernel" }, + { name = "lightning" }, + { name = "matlab" }, + { name = "matplotlib" }, + { name = "numpy" }, + { name = "pandas" }, + { name = "pytest" }, + { name = "reportlab" }, + { name = "scikit-learn" }, + { name = "torch" }, +] + +[package.metadata] +requires-dist = [ + { name = "cvxpy", specifier = ">=1.7.3" }, + { name = "filterpy", specifier = ">=1.4.5" }, + { name = "ipykernel", specifier = ">=7.1.0" }, + { name = "lightning", specifier = ">=2.5.5" }, + { name = "matlab", specifier = ">=0.1" }, + { name = "matplotlib", specifier = ">=3.10.7" }, + { name = "numpy", specifier = ">=2.3.4" }, + { name = "pandas", specifier = ">=2.3.3" }, + { name = "pytest", specifier = ">=8.4.2" }, + { name = "reportlab", specifier = ">=4.4.4" }, + { name = "scikit-learn", specifier = ">=1.5.0" }, + { name = "torch", specifier = ">=2.9.0" }, +] + +[[package]] +name = "executing" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, +] + +[[package]] +name = "filelock" +version = "3.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/46/0028a82567109b5ef6e4d2a1f04a583fb513e6cf9527fcdd09afd817deeb/filelock-3.20.0.tar.gz", hash = "sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4", size = 18922, upload-time = "2025-10-08T18:03:50.056Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl", hash = "sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2", size = 16054, upload-time = "2025-10-08T18:03:48.35Z" }, +] + +[[package]] +name = "filterpy" +version = "1.4.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "matplotlib" }, + { name = "numpy" }, + { name = "scipy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/1d/ac8914360460fafa1990890259b7fa5ef7ba4cd59014e782e4ab3ab144d8/filterpy-1.4.5.zip", hash = "sha256:4f2a4d39e4ea601b9ab42b2db08b5918a9538c168cff1c6895ae26646f3d73b1", size = 177985, upload-time = "2018-10-10T22:38:24.63Z" } + +[[package]] +name = "fonttools" +version = "4.60.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/42/97a13e47a1e51a5a7142475bbcf5107fe3a68fc34aef331c897d5fb98ad0/fonttools-4.60.1.tar.gz", hash = "sha256:ef00af0439ebfee806b25f24c8f92109157ff3fac5731dc7867957812e87b8d9", size = 3559823, upload-time = "2025-09-29T21:13:27.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/85/639aa9bface1537e0fb0f643690672dde0695a5bbbc90736bc571b0b1941/fonttools-4.60.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7b4c32e232a71f63a5d00259ca3d88345ce2a43295bb049d21061f338124246f", size = 2831872, upload-time = "2025-09-29T21:11:20.329Z" }, + { url = "https://files.pythonhosted.org/packages/6b/47/3c63158459c95093be9618794acb1067b3f4d30dcc5c3e8114b70e67a092/fonttools-4.60.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3630e86c484263eaac71d117085d509cbcf7b18f677906824e4bace598fb70d2", size = 2356990, upload-time = "2025-09-29T21:11:22.754Z" }, + { url = "https://files.pythonhosted.org/packages/94/dd/1934b537c86fcf99f9761823f1fc37a98fbd54568e8e613f29a90fed95a9/fonttools-4.60.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5c1015318e4fec75dd4943ad5f6a206d9727adf97410d58b7e32ab644a807914", size = 5042189, upload-time = "2025-09-29T21:11:25.061Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d2/9f4e4c4374dd1daa8367784e1bd910f18ba886db1d6b825b12edf6db3edc/fonttools-4.60.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e6c58beb17380f7c2ea181ea11e7db8c0ceb474c9dd45f48e71e2cb577d146a1", size = 4978683, upload-time = "2025-09-29T21:11:27.693Z" }, + { url = "https://files.pythonhosted.org/packages/cc/c4/0fb2dfd1ecbe9a07954cc13414713ed1eab17b1c0214ef07fc93df234a47/fonttools-4.60.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ec3681a0cb34c255d76dd9d865a55f260164adb9fa02628415cdc2d43ee2c05d", size = 5021372, upload-time = "2025-09-29T21:11:30.257Z" }, + { url = "https://files.pythonhosted.org/packages/0c/d5/495fc7ae2fab20223cc87179a8f50f40f9a6f821f271ba8301ae12bb580f/fonttools-4.60.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f4b5c37a5f40e4d733d3bbaaef082149bee5a5ea3156a785ff64d949bd1353fa", size = 5132562, upload-time = "2025-09-29T21:11:32.737Z" }, + { url = "https://files.pythonhosted.org/packages/bc/fa/021dab618526323c744e0206b3f5c8596a2e7ae9aa38db5948a131123e83/fonttools-4.60.1-cp311-cp311-win32.whl", hash = "sha256:398447f3d8c0c786cbf1209711e79080a40761eb44b27cdafffb48f52bcec258", size = 2230288, upload-time = "2025-09-29T21:11:35.015Z" }, + { url = "https://files.pythonhosted.org/packages/bb/78/0e1a6d22b427579ea5c8273e1c07def2f325b977faaf60bb7ddc01456cb1/fonttools-4.60.1-cp311-cp311-win_amd64.whl", hash = "sha256:d066ea419f719ed87bc2c99a4a4bfd77c2e5949cb724588b9dd58f3fd90b92bf", size = 2278184, upload-time = "2025-09-29T21:11:37.434Z" }, + { url = "https://files.pythonhosted.org/packages/c7/93/0dd45cd283c32dea1545151d8c3637b4b8c53cdb3a625aeb2885b184d74d/fonttools-4.60.1-py3-none-any.whl", hash = "sha256:906306ac7afe2156fcf0042173d6ebbb05416af70f6b370967b47f8f00103bbb", size = 1143175, upload-time = "2025-09-29T21:13:24.134Z" }, +] + +[[package]] +name = "frozenlist" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/03/077f869d540370db12165c0aa51640a873fb661d8b315d1d4d67b284d7ac/frozenlist-1.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09474e9831bc2b2199fad6da3c14c7b0fbdd377cce9d3d77131be28906cb7d84", size = 86912, upload-time = "2025-10-06T05:35:45.98Z" }, + { url = "https://files.pythonhosted.org/packages/df/b5/7610b6bd13e4ae77b96ba85abea1c8cb249683217ef09ac9e0ae93f25a91/frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9", size = 50046, upload-time = "2025-10-06T05:35:47.009Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ef/0e8f1fe32f8a53dd26bdd1f9347efe0778b0fddf62789ea683f4cc7d787d/frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93", size = 50119, upload-time = "2025-10-06T05:35:48.38Z" }, + { url = "https://files.pythonhosted.org/packages/11/b1/71a477adc7c36e5fb628245dfbdea2166feae310757dea848d02bd0689fd/frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2552f44204b744fba866e573be4c1f9048d6a324dfe14475103fd51613eb1d1f", size = 231067, upload-time = "2025-10-06T05:35:49.97Z" }, + { url = "https://files.pythonhosted.org/packages/45/7e/afe40eca3a2dc19b9904c0f5d7edfe82b5304cb831391edec0ac04af94c2/frozenlist-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e7c38f250991e48a9a73e6423db1bb9dd14e722a10f6b8bb8e16a0f55f695", size = 233160, upload-time = "2025-10-06T05:35:51.729Z" }, + { url = "https://files.pythonhosted.org/packages/a6/aa/7416eac95603ce428679d273255ffc7c998d4132cfae200103f164b108aa/frozenlist-1.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8585e3bb2cdea02fc88ffa245069c36555557ad3609e83be0ec71f54fd4abb52", size = 228544, upload-time = "2025-10-06T05:35:53.246Z" }, + { url = "https://files.pythonhosted.org/packages/8b/3d/2a2d1f683d55ac7e3875e4263d28410063e738384d3adc294f5ff3d7105e/frozenlist-1.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:edee74874ce20a373d62dc28b0b18b93f645633c2943fd90ee9d898550770581", size = 243797, upload-time = "2025-10-06T05:35:54.497Z" }, + { url = "https://files.pythonhosted.org/packages/78/1e/2d5565b589e580c296d3bb54da08d206e797d941a83a6fdea42af23be79c/frozenlist-1.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c9a63152fe95756b85f31186bddf42e4c02c6321207fd6601a1c89ebac4fe567", size = 247923, upload-time = "2025-10-06T05:35:55.861Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/65872fcf1d326a7f101ad4d86285c403c87be7d832b7470b77f6d2ed5ddc/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6db2185db9be0a04fecf2f241c70b63b1a242e2805be291855078f2b404dd6b", size = 230886, upload-time = "2025-10-06T05:35:57.399Z" }, + { url = "https://files.pythonhosted.org/packages/a0/76/ac9ced601d62f6956f03cc794f9e04c81719509f85255abf96e2510f4265/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f4be2e3d8bc8aabd566f8d5b8ba7ecc09249d74ba3c9ed52e54dc23a293f0b92", size = 245731, upload-time = "2025-10-06T05:35:58.563Z" }, + { url = "https://files.pythonhosted.org/packages/b9/49/ecccb5f2598daf0b4a1415497eba4c33c1e8ce07495eb07d2860c731b8d5/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c8d1634419f39ea6f5c427ea2f90ca85126b54b50837f31497f3bf38266e853d", size = 241544, upload-time = "2025-10-06T05:35:59.719Z" }, + { url = "https://files.pythonhosted.org/packages/53/4b/ddf24113323c0bbcc54cb38c8b8916f1da7165e07b8e24a717b4a12cbf10/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a7fa382a4a223773ed64242dbe1c9c326ec09457e6b8428efb4118c685c3dfd", size = 241806, upload-time = "2025-10-06T05:36:00.959Z" }, + { url = "https://files.pythonhosted.org/packages/a7/fb/9b9a084d73c67175484ba2789a59f8eebebd0827d186a8102005ce41e1ba/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:11847b53d722050808926e785df837353bd4d75f1d494377e59b23594d834967", size = 229382, upload-time = "2025-10-06T05:36:02.22Z" }, + { url = "https://files.pythonhosted.org/packages/95/a3/c8fb25aac55bf5e12dae5c5aa6a98f85d436c1dc658f21c3ac73f9fa95e5/frozenlist-1.8.0-cp311-cp311-win32.whl", hash = "sha256:27c6e8077956cf73eadd514be8fb04d77fc946a7fe9f7fe167648b0b9085cc25", size = 39647, upload-time = "2025-10-06T05:36:03.409Z" }, + { url = "https://files.pythonhosted.org/packages/0a/f5/603d0d6a02cfd4c8f2a095a54672b3cf967ad688a60fb9faf04fc4887f65/frozenlist-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac913f8403b36a2c8610bbfd25b8013488533e71e62b4b4adce9c86c8cea905b", size = 44064, upload-time = "2025-10-06T05:36:04.368Z" }, + { url = "https://files.pythonhosted.org/packages/5d/16/c2c9ab44e181f043a86f9a8f84d5124b62dbcb3a02c0977ec72b9ac1d3e0/frozenlist-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:d4d3214a0f8394edfa3e303136d0575eece0745ff2b47bd2cb2e66dd92d4351a", size = 39937, upload-time = "2025-10-06T05:36:05.669Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, +] + +[[package]] +name = "fsspec" +version = "2025.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/7f/2747c0d332b9acfa75dc84447a066fdf812b5a6b8d30472b74d309bfe8cb/fsspec-2025.10.0.tar.gz", hash = "sha256:b6789427626f068f9a83ca4e8a3cc050850b6c0f71f99ddb4f542b8266a26a59", size = 309285, upload-time = "2025-10-30T14:58:44.036Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/02/a6b21098b1d5d6249b7c5ab69dde30108a71e4e819d4a9778f1de1d5b70d/fsspec-2025.10.0-py3-none-any.whl", hash = "sha256:7c7712353ae7d875407f97715f0e1ffcc21e33d5b24556cb1e090ae9409ec61d", size = 200966, upload-time = "2025-10-30T14:58:42.53Z" }, +] + +[package.optional-dependencies] +http = [ + { name = "aiohttp" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "ipykernel" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "appnope", marker = "sys_platform == 'darwin'" }, + { name = "comm" }, + { name = "debugpy" }, + { name = "ipython" }, + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "matplotlib-inline" }, + { name = "nest-asyncio" }, + { name = "packaging" }, + { name = "psutil" }, + { name = "pyzmq" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/a4/4948be6eb88628505b83a1f2f40d90254cab66abf2043b3c40fa07dfce0f/ipykernel-7.1.0.tar.gz", hash = "sha256:58a3fc88533d5930c3546dc7eac66c6d288acde4f801e2001e65edc5dc9cf0db", size = 174579, upload-time = "2025-10-27T09:46:39.471Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/17/20c2552266728ceba271967b87919664ecc0e33efca29c3efc6baf88c5f9/ipykernel-7.1.0-py3-none-any.whl", hash = "sha256:763b5ec6c5b7776f6a8d7ce09b267693b4e5ce75cb50ae696aaefb3c85e1ea4c", size = 117968, upload-time = "2025-10-27T09:46:37.805Z" }, +] + +[[package]] +name = "ipython" +version = "9.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "decorator" }, + { name = "ipython-pygments-lexers" }, + { name = "jedi" }, + { name = "matplotlib-inline" }, + { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit" }, + { name = "pygments" }, + { name = "stack-data" }, + { name = "traitlets" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2a/34/29b18c62e39ee2f7a6a3bba7efd952729d8aadd45ca17efc34453b717665/ipython-9.6.0.tar.gz", hash = "sha256:5603d6d5d356378be5043e69441a072b50a5b33b4503428c77b04cb8ce7bc731", size = 4396932, upload-time = "2025-09-29T10:55:53.948Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.0-py3-none-any.whl", hash = "sha256:5f77efafc886d2f023442479b8149e7d86547ad0a979e9da9f045d252f648196", size = 616170, upload-time = "2025-09-29T10:55:47.676Z" }, +] + +[[package]] +name = "ipython-pygments-lexers" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074, upload-time = "2025-01-17T11:24:33.271Z" }, +] + +[[package]] +name = "jedi" +version = "0.19.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "parso" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287, upload-time = "2024-11-11T01:41:42.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278, upload-time = "2024-11-11T01:41:40.175Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "joblib" +version = "1.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/5d/447af5ea094b9e4c4054f82e223ada074c552335b9b4b2d14bd9b35a67c4/joblib-1.5.2.tar.gz", hash = "sha256:3faa5c39054b2f03ca547da9b2f52fde67c06240c31853f306aea97f13647b55", size = 331077, upload-time = "2025-08-27T12:15:46.575Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/e8/685f47e0d754320684db4425a0967f7d3fa70126bffd76110b7009a0090f/joblib-1.5.2-py3-none-any.whl", hash = "sha256:4e1f0bdbb987e6d843c70cf43714cb276623def372df3c22fe5266b2670bc241", size = 308396, upload-time = "2025-08-27T12:15:45.188Z" }, +] + +[[package]] +name = "jupyter-client" +version = "8.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-core" }, + { name = "python-dateutil" }, + { name = "pyzmq" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/22/bf9f12fdaeae18019a468b68952a60fe6dbab5d67cd2a103cac7659b41ca/jupyter_client-8.6.3.tar.gz", hash = "sha256:35b3a0947c4a6e9d589eb97d7d4cd5e90f910ee73101611f01283732bd6d9419", size = 342019, upload-time = "2024-09-17T10:44:17.613Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl", hash = "sha256:e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f", size = 106105, upload-time = "2024-09-17T10:44:15.218Z" }, +] + +[[package]] +name = "jupyter-core" +version = "5.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "platformdirs" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/49/9d1284d0dc65e2c757b74c6687b6d319b02f822ad039e5c512df9194d9dd/jupyter_core-5.9.1.tar.gz", hash = "sha256:4d09aaff303b9566c3ce657f580bd089ff5c91f5f89cf7d8846c3cdf465b5508", size = 89814, upload-time = "2025-10-16T19:19:18.444Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl", hash = "sha256:ebf87fdc6073d142e114c72c9e29a9d7ca03fad818c5d300ce2adc1fb0743407", size = 29032, upload-time = "2025-10-16T19:19:16.783Z" }, +] + +[[package]] +name = "kiwisolver" +version = "1.4.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/3c/85844f1b0feb11ee581ac23fe5fce65cd049a200c1446708cc1b7f922875/kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d", size = 97564, upload-time = "2025-08-10T21:27:49.279Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/ab/c80b0d5a9d8a1a65f4f815f2afff9798b12c3b9f31f1d304dd233dd920e2/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eb14a5da6dc7642b0f3a18f13654847cd8b7a2550e2645a5bda677862b03ba16", size = 124167, upload-time = "2025-08-10T21:25:53.403Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c0/27fe1a68a39cf62472a300e2879ffc13c0538546c359b86f149cc19f6ac3/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:39a219e1c81ae3b103643d2aedb90f1ef22650deb266ff12a19e7773f3e5f089", size = 66579, upload-time = "2025-08-10T21:25:54.79Z" }, + { url = "https://files.pythonhosted.org/packages/31/a2/a12a503ac1fd4943c50f9822678e8015a790a13b5490354c68afb8489814/kiwisolver-1.4.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2405a7d98604b87f3fc28b1716783534b1b4b8510d8142adca34ee0bc3c87543", size = 65309, upload-time = "2025-08-10T21:25:55.76Z" }, + { url = "https://files.pythonhosted.org/packages/66/e1/e533435c0be77c3f64040d68d7a657771194a63c279f55573188161e81ca/kiwisolver-1.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dc1ae486f9abcef254b5618dfb4113dd49f94c68e3e027d03cf0143f3f772b61", size = 1435596, upload-time = "2025-08-10T21:25:56.861Z" }, + { url = "https://files.pythonhosted.org/packages/67/1e/51b73c7347f9aabdc7215aa79e8b15299097dc2f8e67dee2b095faca9cb0/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a1f570ce4d62d718dce3f179ee78dac3b545ac16c0c04bb363b7607a949c0d1", size = 1246548, upload-time = "2025-08-10T21:25:58.246Z" }, + { url = "https://files.pythonhosted.org/packages/21/aa/72a1c5d1e430294f2d32adb9542719cfb441b5da368d09d268c7757af46c/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb27e7b78d716c591e88e0a09a2139c6577865d7f2e152488c2cc6257f460872", size = 1263618, upload-time = "2025-08-10T21:25:59.857Z" }, + { url = "https://files.pythonhosted.org/packages/a3/af/db1509a9e79dbf4c260ce0cfa3903ea8945f6240e9e59d1e4deb731b1a40/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:15163165efc2f627eb9687ea5f3a28137217d217ac4024893d753f46bce9de26", size = 1317437, upload-time = "2025-08-10T21:26:01.105Z" }, + { url = "https://files.pythonhosted.org/packages/e0/f2/3ea5ee5d52abacdd12013a94130436e19969fa183faa1e7c7fbc89e9a42f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bdee92c56a71d2b24c33a7d4c2856bd6419d017e08caa7802d2963870e315028", size = 2195742, upload-time = "2025-08-10T21:26:02.675Z" }, + { url = "https://files.pythonhosted.org/packages/6f/9b/1efdd3013c2d9a2566aa6a337e9923a00590c516add9a1e89a768a3eb2fc/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:412f287c55a6f54b0650bd9b6dce5aceddb95864a1a90c87af16979d37c89771", size = 2290810, upload-time = "2025-08-10T21:26:04.009Z" }, + { url = "https://files.pythonhosted.org/packages/fb/e5/cfdc36109ae4e67361f9bc5b41323648cb24a01b9ade18784657e022e65f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2c93f00dcba2eea70af2be5f11a830a742fe6b579a1d4e00f47760ef13be247a", size = 2461579, upload-time = "2025-08-10T21:26:05.317Z" }, + { url = "https://files.pythonhosted.org/packages/62/86/b589e5e86c7610842213994cdea5add00960076bef4ae290c5fa68589cac/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f117e1a089d9411663a3207ba874f31be9ac8eaa5b533787024dc07aeb74f464", size = 2268071, upload-time = "2025-08-10T21:26:06.686Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c6/f8df8509fd1eee6c622febe54384a96cfaf4d43bf2ccec7a0cc17e4715c9/kiwisolver-1.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:be6a04e6c79819c9a8c2373317d19a96048e5a3f90bec587787e86a1153883c2", size = 73840, upload-time = "2025-08-10T21:26:07.94Z" }, + { url = "https://files.pythonhosted.org/packages/e2/2d/16e0581daafd147bc11ac53f032a2b45eabac897f42a338d0a13c1e5c436/kiwisolver-1.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:0ae37737256ba2de764ddc12aed4956460277f00c4996d51a197e72f62f5eec7", size = 65159, upload-time = "2025-08-10T21:26:09.048Z" }, + { url = "https://files.pythonhosted.org/packages/a3/0f/36d89194b5a32c054ce93e586d4049b6c2c22887b0eb229c61c68afd3078/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:720e05574713db64c356e86732c0f3c5252818d05f9df320f0ad8380641acea5", size = 60104, upload-time = "2025-08-10T21:27:43.287Z" }, + { url = "https://files.pythonhosted.org/packages/52/ba/4ed75f59e4658fd21fe7dde1fee0ac397c678ec3befba3fe6482d987af87/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:17680d737d5335b552994a2008fab4c851bcd7de33094a82067ef3a576ff02fa", size = 58592, upload-time = "2025-08-10T21:27:44.314Z" }, + { url = "https://files.pythonhosted.org/packages/33/01/a8ea7c5ea32a9b45ceeaee051a04c8ed4320f5add3c51bfa20879b765b70/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85b5352f94e490c028926ea567fc569c52ec79ce131dadb968d3853e809518c2", size = 80281, upload-time = "2025-08-10T21:27:45.369Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/dbd2ecdce306f1d07a1aaf324817ee993aab7aee9db47ceac757deabafbe/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:464415881e4801295659462c49461a24fb107c140de781d55518c4b80cb6790f", size = 78009, upload-time = "2025-08-10T21:27:46.376Z" }, + { url = "https://files.pythonhosted.org/packages/da/e9/0d4add7873a73e462aeb45c036a2dead2562b825aa46ba326727b3f31016/kiwisolver-1.4.9-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:fb940820c63a9590d31d88b815e7a3aa5915cad3ce735ab45f0c730b39547de1", size = 73929, upload-time = "2025-08-10T21:27:48.236Z" }, +] + +[[package]] +name = "lightning" +version = "2.5.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fsspec", extra = ["http"] }, + { name = "lightning-utilities" }, + { name = "packaging" }, + { name = "pytorch-lightning" }, + { name = "pyyaml" }, + { name = "torch" }, + { name = "torchmetrics" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/dd/86bb3bebadcdbc6e6e5a63657f0a03f74cd065b5ea965896679f76fec0b4/lightning-2.5.5.tar.gz", hash = "sha256:4d3d66c5b1481364a7e6a1ce8ddde1777a04fa740a3145ec218a9941aed7dd30", size = 640770, upload-time = "2025-09-05T16:01:21.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/d0/4b4fbafc3b18df91207a6e46782d9fd1905f9f45cb2c3b8dfbb239aef781/lightning-2.5.5-py3-none-any.whl", hash = "sha256:69eb248beadd7b600bf48eff00a0ec8af171ec7a678d23787c4aedf12e225e8f", size = 828490, upload-time = "2025-09-05T16:01:17.845Z" }, +] + +[[package]] +name = "lightning-utilities" +version = "0.15.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "setuptools" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b8/39/6fc58ca81492db047149b4b8fd385aa1bfb8c28cd7cacb0c7eb0c44d842f/lightning_utilities-0.15.2.tar.gz", hash = "sha256:cdf12f530214a63dacefd713f180d1ecf5d165338101617b4742e8f22c032e24", size = 31090, upload-time = "2025-08-06T13:57:39.242Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/73/3d757cb3fc16f0f9794dd289bcd0c4a031d9cf54d8137d6b984b2d02edf3/lightning_utilities-0.15.2-py3-none-any.whl", hash = "sha256:ad3ab1703775044bbf880dbf7ddaaac899396c96315f3aa1779cec9d618a9841", size = 29431, upload-time = "2025-08-06T13:57:38.046Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, + { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, + { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, +] + +[[package]] +name = "matlab" +version = "0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/ea/1ecc1d9dfde594111b395dff9637183225dfb55b54fff4a906ba0ad5095a/matlab-0.1.tar.gz", hash = "sha256:8b13f7f121fcfb138203e35fa931e5f434abeefccfc583f8b3b4e2c0d0e7bcae", size = 538, upload-time = "2014-10-03T22:43:53.169Z" } + +[[package]] +name = "matplotlib" +version = "3.10.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "contourpy" }, + { name = "cycler" }, + { name = "fonttools" }, + { name = "kiwisolver" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/e2/d2d5295be2f44c678ebaf3544ba32d20c1f9ef08c49fe47f496180e1db15/matplotlib-3.10.7.tar.gz", hash = "sha256:a06ba7e2a2ef9131c79c49e63dad355d2d878413a0376c1727c8b9335ff731c7", size = 34804865, upload-time = "2025-10-09T00:28:00.669Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/bc/0fb489005669127ec13f51be0c6adc074d7cf191075dab1da9fe3b7a3cfc/matplotlib-3.10.7-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:53b492410a6cd66c7a471de6c924f6ede976e963c0f3097a3b7abfadddc67d0a", size = 8257507, upload-time = "2025-10-09T00:26:19.073Z" }, + { url = "https://files.pythonhosted.org/packages/e2/6a/d42588ad895279ff6708924645b5d2ed54a7fb2dc045c8a804e955aeace1/matplotlib-3.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d9749313deb729f08207718d29c86246beb2ea3fdba753595b55901dee5d2fd6", size = 8119565, upload-time = "2025-10-09T00:26:21.023Z" }, + { url = "https://files.pythonhosted.org/packages/10/b7/4aa196155b4d846bd749cf82aa5a4c300cf55a8b5e0dfa5b722a63c0f8a0/matplotlib-3.10.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2222c7ba2cbde7fe63032769f6eb7e83ab3227f47d997a8453377709b7fe3a5a", size = 8692668, upload-time = "2025-10-09T00:26:22.967Z" }, + { url = "https://files.pythonhosted.org/packages/e6/e7/664d2b97016f46683a02d854d730cfcf54ff92c1dafa424beebef50f831d/matplotlib-3.10.7-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e91f61a064c92c307c5a9dc8c05dc9f8a68f0a3be199d9a002a0622e13f874a1", size = 9521051, upload-time = "2025-10-09T00:26:25.041Z" }, + { url = "https://files.pythonhosted.org/packages/a8/a3/37aef1404efa615f49b5758a5e0261c16dd88f389bc1861e722620e4a754/matplotlib-3.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6f1851eab59ca082c95df5a500106bad73672645625e04538b3ad0f69471ffcc", size = 9576878, upload-time = "2025-10-09T00:26:27.478Z" }, + { url = "https://files.pythonhosted.org/packages/33/cd/b145f9797126f3f809d177ca378de57c45413c5099c5990de2658760594a/matplotlib-3.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:6516ce375109c60ceec579e699524e9d504cd7578506f01150f7a6bc174a775e", size = 8115142, upload-time = "2025-10-09T00:26:29.774Z" }, + { url = "https://files.pythonhosted.org/packages/2e/39/63bca9d2b78455ed497fcf51a9c71df200a11048f48249038f06447fa947/matplotlib-3.10.7-cp311-cp311-win_arm64.whl", hash = "sha256:b172db79759f5f9bc13ef1c3ef8b9ee7b37b0247f987fbbbdaa15e4f87fd46a9", size = 7992439, upload-time = "2025-10-09T00:26:40.32Z" }, + { url = "https://files.pythonhosted.org/packages/58/8f/76d5dc21ac64a49e5498d7f0472c0781dae442dd266a67458baec38288ec/matplotlib-3.10.7-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:15112bcbaef211bd663fa935ec33313b948e214454d949b723998a43357b17b0", size = 8252283, upload-time = "2025-10-09T00:27:54.739Z" }, + { url = "https://files.pythonhosted.org/packages/27/0d/9c5d4c2317feb31d819e38c9f947c942f42ebd4eb935fc6fd3518a11eaa7/matplotlib-3.10.7-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d2a959c640cdeecdd2ec3136e8ea0441da59bcaf58d67e9c590740addba2cb68", size = 8116733, upload-time = "2025-10-09T00:27:56.406Z" }, + { url = "https://files.pythonhosted.org/packages/9a/cc/3fe688ff1355010937713164caacf9ed443675ac48a997bab6ed23b3f7c0/matplotlib-3.10.7-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3886e47f64611046bc1db523a09dd0a0a6bed6081e6f90e13806dd1d1d1b5e91", size = 8693919, upload-time = "2025-10-09T00:27:58.41Z" }, +] + +[[package]] +name = "matplotlib-inline" +version = "0.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/74/97e72a36efd4ae2bccb3463284300f8953f199b5ffbc04cbbb0ec78f74b1/matplotlib_inline-0.2.1.tar.gz", hash = "sha256:e1ee949c340d771fc39e241ea75683deb94762c8fa5f2927ec57c83c4dffa9fe", size = 8110, upload-time = "2025-10-23T09:00:22.126Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/33/ee4519fa02ed11a94aef9559552f3b17bb863f2ecfe1a35dc7f548cde231/matplotlib_inline-0.2.1-py3-none-any.whl", hash = "sha256:d56ce5156ba6085e00a9d54fead6ed29a9c47e215cd1bba2e976ef39f5710a76", size = 9516, upload-time = "2025-10-23T09:00:20.675Z" }, +] + +[[package]] +name = "mpmath" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" }, +] + +[[package]] +name = "multidict" +version = "6.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/80/1e/5492c365f222f907de1039b91f922b93fa4f764c713ee858d235495d8f50/multidict-6.7.0.tar.gz", hash = "sha256:c6e99d9a65ca282e578dfea819cfa9c0a62b2499d8677392e09feaf305e9e6f5", size = 101834, upload-time = "2025-10-06T14:52:30.657Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/9e/5c727587644d67b2ed479041e4b1c58e30afc011e3d45d25bbe35781217c/multidict-6.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4d409aa42a94c0b3fa617708ef5276dfe81012ba6753a0370fcc9d0195d0a1fc", size = 76604, upload-time = "2025-10-06T14:48:54.277Z" }, + { url = "https://files.pythonhosted.org/packages/17/e4/67b5c27bd17c085a5ea8f1ec05b8a3e5cba0ca734bfcad5560fb129e70ca/multidict-6.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:14c9e076eede3b54c636f8ce1c9c252b5f057c62131211f0ceeec273810c9721", size = 44715, upload-time = "2025-10-06T14:48:55.445Z" }, + { url = "https://files.pythonhosted.org/packages/4d/e1/866a5d77be6ea435711bef2a4291eed11032679b6b28b56b4776ab06ba3e/multidict-6.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c09703000a9d0fa3c3404b27041e574cc7f4df4c6563873246d0e11812a94b6", size = 44332, upload-time = "2025-10-06T14:48:56.706Z" }, + { url = "https://files.pythonhosted.org/packages/31/61/0c2d50241ada71ff61a79518db85ada85fdabfcf395d5968dae1cbda04e5/multidict-6.7.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a265acbb7bb33a3a2d626afbe756371dce0279e7b17f4f4eda406459c2b5ff1c", size = 245212, upload-time = "2025-10-06T14:48:58.042Z" }, + { url = "https://files.pythonhosted.org/packages/ac/e0/919666a4e4b57fff1b57f279be1c9316e6cdc5de8a8b525d76f6598fefc7/multidict-6.7.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51cb455de290ae462593e5b1cb1118c5c22ea7f0d3620d9940bf695cea5a4bd7", size = 246671, upload-time = "2025-10-06T14:49:00.004Z" }, + { url = "https://files.pythonhosted.org/packages/a1/cc/d027d9c5a520f3321b65adea289b965e7bcbd2c34402663f482648c716ce/multidict-6.7.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:db99677b4457c7a5c5a949353e125ba72d62b35f74e26da141530fbb012218a7", size = 225491, upload-time = "2025-10-06T14:49:01.393Z" }, + { url = "https://files.pythonhosted.org/packages/75/c4/bbd633980ce6155a28ff04e6a6492dd3335858394d7bb752d8b108708558/multidict-6.7.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f470f68adc395e0183b92a2f4689264d1ea4b40504a24d9882c27375e6662bb9", size = 257322, upload-time = "2025-10-06T14:49:02.745Z" }, + { url = "https://files.pythonhosted.org/packages/4c/6d/d622322d344f1f053eae47e033b0b3f965af01212de21b10bcf91be991fb/multidict-6.7.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0db4956f82723cc1c270de9c6e799b4c341d327762ec78ef82bb962f79cc07d8", size = 254694, upload-time = "2025-10-06T14:49:04.15Z" }, + { url = "https://files.pythonhosted.org/packages/a8/9f/78f8761c2705d4c6d7516faed63c0ebdac569f6db1bef95e0d5218fdc146/multidict-6.7.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3e56d780c238f9e1ae66a22d2adf8d16f485381878250db8d496623cd38b22bd", size = 246715, upload-time = "2025-10-06T14:49:05.967Z" }, + { url = "https://files.pythonhosted.org/packages/78/59/950818e04f91b9c2b95aab3d923d9eabd01689d0dcd889563988e9ea0fd8/multidict-6.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9d14baca2ee12c1a64740d4531356ba50b82543017f3ad6de0deb943c5979abb", size = 243189, upload-time = "2025-10-06T14:49:07.37Z" }, + { url = "https://files.pythonhosted.org/packages/7a/3d/77c79e1934cad2ee74991840f8a0110966d9599b3af95964c0cd79bb905b/multidict-6.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:295a92a76188917c7f99cda95858c822f9e4aae5824246bba9b6b44004ddd0a6", size = 237845, upload-time = "2025-10-06T14:49:08.759Z" }, + { url = "https://files.pythonhosted.org/packages/63/1b/834ce32a0a97a3b70f86437f685f880136677ac00d8bce0027e9fd9c2db7/multidict-6.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:39f1719f57adbb767ef592a50ae5ebb794220d1188f9ca93de471336401c34d2", size = 246374, upload-time = "2025-10-06T14:49:10.574Z" }, + { url = "https://files.pythonhosted.org/packages/23/ef/43d1c3ba205b5dec93dc97f3fba179dfa47910fc73aaaea4f7ceb41cec2a/multidict-6.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:0a13fb8e748dfc94749f622de065dd5c1def7e0d2216dba72b1d8069a389c6ff", size = 253345, upload-time = "2025-10-06T14:49:12.331Z" }, + { url = "https://files.pythonhosted.org/packages/6b/03/eaf95bcc2d19ead522001f6a650ef32811aa9e3624ff0ad37c445c7a588c/multidict-6.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e3aa16de190d29a0ea1b48253c57d99a68492c8dd8948638073ab9e74dc9410b", size = 246940, upload-time = "2025-10-06T14:49:13.821Z" }, + { url = "https://files.pythonhosted.org/packages/e8/df/ec8a5fd66ea6cd6f525b1fcbb23511b033c3e9bc42b81384834ffa484a62/multidict-6.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a048ce45dcdaaf1defb76b2e684f997fb5abf74437b6cb7b22ddad934a964e34", size = 242229, upload-time = "2025-10-06T14:49:15.603Z" }, + { url = "https://files.pythonhosted.org/packages/8a/a2/59b405d59fd39ec86d1142630e9049243015a5f5291ba49cadf3c090c541/multidict-6.7.0-cp311-cp311-win32.whl", hash = "sha256:a90af66facec4cebe4181b9e62a68be65e45ac9b52b67de9eec118701856e7ff", size = 41308, upload-time = "2025-10-06T14:49:16.871Z" }, + { url = "https://files.pythonhosted.org/packages/32/0f/13228f26f8b882c34da36efa776c3b7348455ec383bab4a66390e42963ae/multidict-6.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:95b5ffa4349df2887518bb839409bcf22caa72d82beec453216802f475b23c81", size = 46037, upload-time = "2025-10-06T14:49:18.457Z" }, + { url = "https://files.pythonhosted.org/packages/84/1f/68588e31b000535a3207fd3c909ebeec4fb36b52c442107499c18a896a2a/multidict-6.7.0-cp311-cp311-win_arm64.whl", hash = "sha256:329aa225b085b6f004a4955271a7ba9f1087e39dcb7e65f6284a988264a63912", size = 43023, upload-time = "2025-10-06T14:49:19.648Z" }, + { url = "https://files.pythonhosted.org/packages/b7/da/7d22601b625e241d4f23ef1ebff8acfc60da633c9e7e7922e24d10f592b3/multidict-6.7.0-py3-none-any.whl", hash = "sha256:394fc5c42a333c9ffc3e421a4c85e08580d990e08b99f6bf35b4132114c5dcb3", size = 12317, upload-time = "2025-10-06T14:52:29.272Z" }, +] + +[[package]] +name = "nest-asyncio" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418, upload-time = "2024-01-21T14:25:19.227Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" }, +] + +[[package]] +name = "networkx" +version = "3.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/4f/ccdb8ad3a38e583f214547fd2f7ff1fc160c43a75af88e6aec213404b96a/networkx-3.5.tar.gz", hash = "sha256:d4c6f9cf81f52d69230866796b82afbccdec3db7ae4fbd1b65ea750feed50037", size = 2471065, upload-time = "2025-05-29T11:35:07.804Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/8d/776adee7bbf76365fdd7f2552710282c79a4ead5d2a46408c9043a2b70ba/networkx-3.5-py3-none-any.whl", hash = "sha256:0030d386a9a06dee3565298b4a734b68589749a544acbb6c412dc9e2489ec6ec", size = 2034406, upload-time = "2025-05-29T11:35:04.961Z" }, +] + +[[package]] +name = "numpy" +version = "2.3.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/f4/098d2270d52b41f1bd7db9fc288aaa0400cb48c2a3e2af6fa365d9720947/numpy-2.3.4.tar.gz", hash = "sha256:a7d018bfedb375a8d979ac758b120ba846a7fe764911a64465fd87b8729f4a6a", size = 20582187, upload-time = "2025-10-15T16:18:11.77Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/e7/0e07379944aa8afb49a556a2b54587b828eb41dc9adc56fb7615b678ca53/numpy-2.3.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e78aecd2800b32e8347ce49316d3eaf04aed849cd5b38e0af39f829a4e59f5eb", size = 21259519, upload-time = "2025-10-15T16:15:19.012Z" }, + { url = "https://files.pythonhosted.org/packages/d0/cb/5a69293561e8819b09e34ed9e873b9a82b5f2ade23dce4c51dc507f6cfe1/numpy-2.3.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7fd09cc5d65bda1e79432859c40978010622112e9194e581e3415a3eccc7f43f", size = 14452796, upload-time = "2025-10-15T16:15:23.094Z" }, + { url = "https://files.pythonhosted.org/packages/e4/04/ff11611200acd602a1e5129e36cfd25bf01ad8e5cf927baf2e90236eb02e/numpy-2.3.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:1b219560ae2c1de48ead517d085bc2d05b9433f8e49d0955c82e8cd37bd7bf36", size = 5381639, upload-time = "2025-10-15T16:15:25.572Z" }, + { url = "https://files.pythonhosted.org/packages/ea/77/e95c757a6fe7a48d28a009267408e8aa382630cc1ad1db7451b3bc21dbb4/numpy-2.3.4-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:bafa7d87d4c99752d07815ed7a2c0964f8ab311eb8168f41b910bd01d15b6032", size = 6914296, upload-time = "2025-10-15T16:15:27.079Z" }, + { url = "https://files.pythonhosted.org/packages/a3/d2/137c7b6841c942124eae921279e5c41b1c34bab0e6fc60c7348e69afd165/numpy-2.3.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36dc13af226aeab72b7abad501d370d606326a0029b9f435eacb3b8c94b8a8b7", size = 14591904, upload-time = "2025-10-15T16:15:29.044Z" }, + { url = "https://files.pythonhosted.org/packages/bb/32/67e3b0f07b0aba57a078c4ab777a9e8e6bc62f24fb53a2337f75f9691699/numpy-2.3.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7b2f9a18b5ff9824a6af80de4f37f4ec3c2aab05ef08f51c77a093f5b89adda", size = 16939602, upload-time = "2025-10-15T16:15:31.106Z" }, + { url = "https://files.pythonhosted.org/packages/95/22/9639c30e32c93c4cee3ccdb4b09c2d0fbff4dcd06d36b357da06146530fb/numpy-2.3.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9984bd645a8db6ca15d850ff996856d8762c51a2239225288f08f9050ca240a0", size = 16372661, upload-time = "2025-10-15T16:15:33.546Z" }, + { url = "https://files.pythonhosted.org/packages/12/e9/a685079529be2b0156ae0c11b13d6be647743095bb51d46589e95be88086/numpy-2.3.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:64c5825affc76942973a70acf438a8ab618dbd692b84cd5ec40a0a0509edc09a", size = 18884682, upload-time = "2025-10-15T16:15:36.105Z" }, + { url = "https://files.pythonhosted.org/packages/cf/85/f6f00d019b0cc741e64b4e00ce865a57b6bed945d1bbeb1ccadbc647959b/numpy-2.3.4-cp311-cp311-win32.whl", hash = "sha256:ed759bf7a70342f7817d88376eb7142fab9fef8320d6019ef87fae05a99874e1", size = 6570076, upload-time = "2025-10-15T16:15:38.225Z" }, + { url = "https://files.pythonhosted.org/packages/7d/10/f8850982021cb90e2ec31990291f9e830ce7d94eef432b15066e7cbe0bec/numpy-2.3.4-cp311-cp311-win_amd64.whl", hash = "sha256:faba246fb30ea2a526c2e9645f61612341de1a83fb1e0c5edf4ddda5a9c10996", size = 13089358, upload-time = "2025-10-15T16:15:40.404Z" }, + { url = "https://files.pythonhosted.org/packages/d1/ad/afdd8351385edf0b3445f9e24210a9c3971ef4de8fd85155462fc4321d79/numpy-2.3.4-cp311-cp311-win_arm64.whl", hash = "sha256:4c01835e718bcebe80394fd0ac66c07cbb90147ebbdad3dcecd3f25de2ae7e2c", size = 10462292, upload-time = "2025-10-15T16:15:42.896Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b6/64898f51a86ec88ca1257a59c1d7fd077b60082a119affefcdf1dd0df8ca/numpy-2.3.4-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6e274603039f924c0fe5cb73438fa9246699c78a6df1bd3decef9ae592ae1c05", size = 21131552, upload-time = "2025-10-15T16:17:55.845Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4c/f135dc6ebe2b6a3c77f4e4838fa63d350f85c99462012306ada1bd4bc460/numpy-2.3.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d149aee5c72176d9ddbc6803aef9c0f6d2ceeea7626574fc68518da5476fa346", size = 14377796, upload-time = "2025-10-15T16:17:58.308Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a4/f33f9c23fcc13dd8412fc8614559b5b797e0aba9d8e01dfa8bae10c84004/numpy-2.3.4-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:6d34ed9db9e6395bb6cd33286035f73a59b058169733a9db9f85e650b88df37e", size = 5306904, upload-time = "2025-10-15T16:18:00.596Z" }, + { url = "https://files.pythonhosted.org/packages/28/af/c44097f25f834360f9fb960fa082863e0bad14a42f36527b2a121abdec56/numpy-2.3.4-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:fdebe771ca06bb8d6abce84e51dca9f7921fe6ad34a0c914541b063e9a68928b", size = 6819682, upload-time = "2025-10-15T16:18:02.32Z" }, + { url = "https://files.pythonhosted.org/packages/c5/8c/cd283b54c3c2b77e188f63e23039844f56b23bba1712318288c13fe86baf/numpy-2.3.4-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e92defe6c08211eb77902253b14fe5b480ebc5112bc741fd5e9cd0608f847", size = 14422300, upload-time = "2025-10-15T16:18:04.271Z" }, + { url = "https://files.pythonhosted.org/packages/b0/f0/8404db5098d92446b3e3695cf41c6f0ecb703d701cb0b7566ee2177f2eee/numpy-2.3.4-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13b9062e4f5c7ee5c7e5be96f29ba71bc5a37fed3d1d77c37390ae00724d296d", size = 16760806, upload-time = "2025-10-15T16:18:06.668Z" }, + { url = "https://files.pythonhosted.org/packages/95/8e/2844c3959ce9a63acc7c8e50881133d86666f0420bcde695e115ced0920f/numpy-2.3.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:81b3a59793523e552c4a96109dde028aa4448ae06ccac5a76ff6532a85558a7f", size = 12973130, upload-time = "2025-10-15T16:18:09.397Z" }, +] + +[[package]] +name = "nvidia-cublas-cu12" +version = "12.8.4.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/61/e24b560ab2e2eaeb3c839129175fb330dfcfc29e5203196e5541a4c44682/nvidia_cublas_cu12-12.8.4.1-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:8ac4e771d5a348c551b2a426eda6193c19aa630236b418086020df5ba9667142", size = 594346921, upload-time = "2025-03-07T01:44:31.254Z" }, +] + +[[package]] +name = "nvidia-cuda-cupti-cu12" +version = "12.8.90" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/02/2adcaa145158bf1a8295d83591d22e4103dbfd821bcaf6f3f53151ca4ffa/nvidia_cuda_cupti_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ea0cb07ebda26bb9b29ba82cda34849e73c166c18162d3913575b0c9db9a6182", size = 10248621, upload-time = "2025-03-07T01:40:21.213Z" }, +] + +[[package]] +name = "nvidia-cuda-nvrtc-cu12" +version = "12.8.93" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/6b/32f747947df2da6994e999492ab306a903659555dddc0fbdeb9d71f75e52/nvidia_cuda_nvrtc_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:a7756528852ef889772a84c6cd89d41dfa74667e24cca16bb31f8f061e3e9994", size = 88040029, upload-time = "2025-03-07T01:42:13.562Z" }, +] + +[[package]] +name = "nvidia-cuda-runtime-cu12" +version = "12.8.90" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/9b/a997b638fcd068ad6e4d53b8551a7d30fe8b404d6f1804abf1df69838932/nvidia_cuda_runtime_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:adade8dcbd0edf427b7204d480d6066d33902cab2a4707dcfc48a2d0fd44ab90", size = 954765, upload-time = "2025-03-07T01:40:01.615Z" }, +] + +[[package]] +name = "nvidia-cudnn-cu12" +version = "9.10.2.21" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas-cu12" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/51/e123d997aa098c61d029f76663dedbfb9bc8dcf8c60cbd6adbe42f76d049/nvidia_cudnn_cu12-9.10.2.21-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:949452be657fa16687d0930933f032835951ef0892b37d2d53824d1a84dc97a8", size = 706758467, upload-time = "2025-06-06T21:54:08.597Z" }, +] + +[[package]] +name = "nvidia-cufft-cu12" +version = "11.3.3.83" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink-cu12" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/13/ee4e00f30e676b66ae65b4f08cb5bcbb8392c03f54f2d5413ea99a5d1c80/nvidia_cufft_cu12-11.3.3.83-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d2dd21ec0b88cf61b62e6b43564355e5222e4a3fb394cac0db101f2dd0d4f74", size = 193118695, upload-time = "2025-03-07T01:45:27.821Z" }, +] + +[[package]] +name = "nvidia-cufile-cu12" +version = "1.13.1.3" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/fe/1bcba1dfbfb8d01be8d93f07bfc502c93fa23afa6fd5ab3fc7c1df71038a/nvidia_cufile_cu12-1.13.1.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1d069003be650e131b21c932ec3d8969c1715379251f8d23a1860554b1cb24fc", size = 1197834, upload-time = "2025-03-07T01:45:50.723Z" }, +] + +[[package]] +name = "nvidia-curand-cu12" +version = "10.3.9.90" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/aa/6584b56dc84ebe9cf93226a5cde4d99080c8e90ab40f0c27bda7a0f29aa1/nvidia_curand_cu12-10.3.9.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:b32331d4f4df5d6eefa0554c565b626c7216f87a06a4f56fab27c3b68a830ec9", size = 63619976, upload-time = "2025-03-07T01:46:23.323Z" }, +] + +[[package]] +name = "nvidia-cusolver-cu12" +version = "11.7.3.90" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas-cu12" }, + { name = "nvidia-cusparse-cu12" }, + { name = "nvidia-nvjitlink-cu12" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/48/9a13d2975803e8cf2777d5ed57b87a0b6ca2cc795f9a4f59796a910bfb80/nvidia_cusolver_cu12-11.7.3.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:4376c11ad263152bd50ea295c05370360776f8c3427b30991df774f9fb26c450", size = 267506905, upload-time = "2025-03-07T01:47:16.273Z" }, +] + +[[package]] +name = "nvidia-cusparse-cu12" +version = "12.5.8.93" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink-cu12" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/f5/e1854cb2f2bcd4280c44736c93550cc300ff4b8c95ebe370d0aa7d2b473d/nvidia_cusparse_cu12-12.5.8.93-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ec05d76bbbd8b61b06a80e1eaf8cf4959c3d4ce8e711b65ebd0443bb0ebb13b", size = 288216466, upload-time = "2025-03-07T01:48:13.779Z" }, +] + +[[package]] +name = "nvidia-cusparselt-cu12" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/79/12978b96bd44274fe38b5dde5cfb660b1d114f70a65ef962bcbbed99b549/nvidia_cusparselt_cu12-0.7.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f1bb701d6b930d5a7cea44c19ceb973311500847f81b634d802b7b539dc55623", size = 287193691, upload-time = "2025-02-26T00:15:44.104Z" }, +] + +[[package]] +name = "nvidia-nccl-cu12" +version = "2.27.5" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/89/f7a07dc961b60645dbbf42e80f2bc85ade7feb9a491b11a1e973aa00071f/nvidia_nccl_cu12-2.27.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ad730cf15cb5d25fe849c6e6ca9eb5b76db16a80f13f425ac68d8e2e55624457", size = 322348229, upload-time = "2025-06-26T04:11:28.385Z" }, +] + +[[package]] +name = "nvidia-nvjitlink-cu12" +version = "12.8.93" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f6/74/86a07f1d0f42998ca31312f998bd3b9a7eff7f52378f4f270c8679c77fb9/nvidia_nvjitlink_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:81ff63371a7ebd6e6451970684f916be2eab07321b73c9d244dc2b4da7f73b88", size = 39254836, upload-time = "2025-03-07T01:49:55.661Z" }, +] + +[[package]] +name = "nvidia-nvshmem-cu12" +version = "3.3.20" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/6c/99acb2f9eb85c29fc6f3a7ac4dccfd992e22666dd08a642b303311326a97/nvidia_nvshmem_cu12-3.3.20-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d00f26d3f9b2e3c3065be895e3059d6479ea5c638a3f38c9fec49b1b9dd7c1e5", size = 124657145, upload-time = "2025-08-04T20:25:19.995Z" }, +] + +[[package]] +name = "nvidia-nvtx-cu12" +version = "12.8.90" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/eb/86626c1bbc2edb86323022371c39aa48df6fd8b0a1647bc274577f72e90b/nvidia_nvtx_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5b17e2001cc0d751a5bc2c6ec6d26ad95913324a4adb86788c944f8ce9ba441f", size = 89954, upload-time = "2025-03-07T01:42:44.131Z" }, +] + +[[package]] +name = "osqp" +version = "1.0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jinja2" }, + { name = "joblib" }, + { name = "numpy" }, + { name = "scipy" }, + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/cf/023078d9985526494901e9ca91c59d17b2d2e5f87a047f4b8b9749ce5922/osqp-1.0.5.tar.gz", hash = "sha256:60b484cf829c99d94bb7ae4e9beb2e0895d94c5e64e074b5b27b6ef887941936", size = 56757, upload-time = "2025-10-15T14:05:33.613Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/01/417ccf73d61b24a00c56fc207db316c72ff86234aa21417d70148447e091/osqp-1.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7bd899ea81ac03030ea0e28a1102797779ffe6450315ad79009c89bb20156887", size = 326124, upload-time = "2025-10-15T14:05:05.069Z" }, + { url = "https://files.pythonhosted.org/packages/1a/ae/dfc315af542489706b5659bb7759de2f29367dee1d6918753d21f2391728/osqp-1.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b837236c847ac90dbd074001dbe5c921701a717fbfebe25f86af93adcad496be", size = 301870, upload-time = "2025-10-15T14:05:06.325Z" }, + { url = "https://files.pythonhosted.org/packages/05/34/7d2478c822edb53a38a3ed2cae89c7a9375e6a8d04897f3fb974c431a189/osqp-1.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e65dde66bf5001a6884082090e7311e1f6881a475e9b6c1b5924d7afa7cd5adc", size = 336553, upload-time = "2025-10-15T14:05:07.437Z" }, + { url = "https://files.pythonhosted.org/packages/82/5f/a3376f56f4d209618c22492fe02b47be05b47bbb6c263460e0f38b36fc1d/osqp-1.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c83f4a164e03fba91c244f6cfaa52acc3e6a93d11b3279a9f768f0a14e82fb18", size = 357238, upload-time = "2025-10-15T14:05:08.66Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c4/d47ccafc3e149c1b9b860c63fbdbaa18dfc06784593cd221c5896be9945c/osqp-1.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:e1f6d0231ea47269ccf3df5587987797a5a2fca4083058ea6d53e2d777c9e3fb", size = 309670, upload-time = "2025-10-15T14:05:09.716Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pandas" +version = "2.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/fa/7ac648108144a095b4fb6aa3de1954689f7af60a14cf25583f4960ecb878/pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523", size = 11578790, upload-time = "2025-09-29T23:18:30.065Z" }, + { url = "https://files.pythonhosted.org/packages/9b/35/74442388c6cf008882d4d4bdfc4109be87e9b8b7ccd097ad1e7f006e2e95/pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45", size = 10833831, upload-time = "2025-09-29T23:38:56.071Z" }, + { url = "https://files.pythonhosted.org/packages/fe/e4/de154cbfeee13383ad58d23017da99390b91d73f8c11856f2095e813201b/pandas-2.3.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66", size = 12199267, upload-time = "2025-09-29T23:18:41.627Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c9/63f8d545568d9ab91476b1818b4741f521646cbdd151c6efebf40d6de6f7/pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b", size = 12789281, upload-time = "2025-09-29T23:18:56.834Z" }, + { url = "https://files.pythonhosted.org/packages/f2/00/a5ac8c7a0e67fd1a6059e40aa08fa1c52cc00709077d2300e210c3ce0322/pandas-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791", size = 13240453, upload-time = "2025-09-29T23:19:09.247Z" }, + { url = "https://files.pythonhosted.org/packages/27/4d/5c23a5bc7bd209231618dd9e606ce076272c9bc4f12023a70e03a86b4067/pandas-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151", size = 13890361, upload-time = "2025-09-29T23:19:25.342Z" }, + { url = "https://files.pythonhosted.org/packages/8e/59/712db1d7040520de7a4965df15b774348980e6df45c129b8c64d0dbe74ef/pandas-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c", size = 11348702, upload-time = "2025-09-29T23:19:38.296Z" }, +] + +[[package]] +name = "parso" +version = "0.8.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d4/de/53e0bcf53d13e005bd8c92e7855142494f41171b34c2536b86187474184d/parso-0.8.5.tar.gz", hash = "sha256:034d7354a9a018bdce352f48b2a8a450f05e9d6ee85db84764e9b6bd96dafe5a", size = 401205, upload-time = "2025-08-23T15:15:28.028Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl", hash = "sha256:646204b5ee239c396d040b90f9e272e9a8017c630092bf59980beb62fd033887", size = 106668, upload-time = "2025-08-23T15:15:25.663Z" }, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" }, +] + +[[package]] +name = "pillow" +version = "12.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/cace85a1b0c9775a9f8f5d5423c8261c858760e2466c79b2dd184638b056/pillow-12.0.0.tar.gz", hash = "sha256:87d4f8125c9988bfbed67af47dd7a953e2fc7b0cc1e7800ec6d2080d490bb353", size = 47008828, upload-time = "2025-10-15T18:24:14.008Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/5a/a2f6773b64edb921a756eb0729068acad9fc5208a53f4a349396e9436721/pillow-12.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0fd00cac9c03256c8b2ff58f162ebcd2587ad3e1f2e397eab718c47e24d231cc", size = 5289798, upload-time = "2025-10-15T18:21:47.763Z" }, + { url = "https://files.pythonhosted.org/packages/2e/05/069b1f8a2e4b5a37493da6c5868531c3f77b85e716ad7a590ef87d58730d/pillow-12.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3475b96f5908b3b16c47533daaa87380c491357d197564e0ba34ae75c0f3257", size = 4650589, upload-time = "2025-10-15T18:21:49.515Z" }, + { url = "https://files.pythonhosted.org/packages/61/e3/2c820d6e9a36432503ead175ae294f96861b07600a7156154a086ba7111a/pillow-12.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:110486b79f2d112cf6add83b28b627e369219388f64ef2f960fef9ebaf54c642", size = 6230472, upload-time = "2025-10-15T18:21:51.052Z" }, + { url = "https://files.pythonhosted.org/packages/4f/89/63427f51c64209c5e23d4d52071c8d0f21024d3a8a487737caaf614a5795/pillow-12.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5269cc1caeedb67e6f7269a42014f381f45e2e7cd42d834ede3c703a1d915fe3", size = 8033887, upload-time = "2025-10-15T18:21:52.604Z" }, + { url = "https://files.pythonhosted.org/packages/f6/1b/c9711318d4901093c15840f268ad649459cd81984c9ec9887756cca049a5/pillow-12.0.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa5129de4e174daccbc59d0a3b6d20eaf24417d59851c07ebb37aeb02947987c", size = 6343964, upload-time = "2025-10-15T18:21:54.619Z" }, + { url = "https://files.pythonhosted.org/packages/41/1e/db9470f2d030b4995083044cd8738cdd1bf773106819f6d8ba12597d5352/pillow-12.0.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bee2a6db3a7242ea309aa7ee8e2780726fed67ff4e5b40169f2c940e7eb09227", size = 7034756, upload-time = "2025-10-15T18:21:56.151Z" }, + { url = "https://files.pythonhosted.org/packages/cc/b0/6177a8bdd5ee4ed87cba2de5a3cc1db55ffbbec6176784ce5bb75aa96798/pillow-12.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:90387104ee8400a7b4598253b4c406f8958f59fcf983a6cea2b50d59f7d63d0b", size = 6458075, upload-time = "2025-10-15T18:21:57.759Z" }, + { url = "https://files.pythonhosted.org/packages/bc/5e/61537aa6fa977922c6a03253a0e727e6e4a72381a80d63ad8eec350684f2/pillow-12.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bc91a56697869546d1b8f0a3ff35224557ae7f881050e99f615e0119bf934b4e", size = 7125955, upload-time = "2025-10-15T18:21:59.372Z" }, + { url = "https://files.pythonhosted.org/packages/1f/3d/d5033539344ee3cbd9a4d69e12e63ca3a44a739eb2d4c8da350a3d38edd7/pillow-12.0.0-cp311-cp311-win32.whl", hash = "sha256:27f95b12453d165099c84f8a8bfdfd46b9e4bda9e0e4b65f0635430027f55739", size = 6298440, upload-time = "2025-10-15T18:22:00.982Z" }, + { url = "https://files.pythonhosted.org/packages/4d/42/aaca386de5cc8bd8a0254516957c1f265e3521c91515b16e286c662854c4/pillow-12.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:b583dc9070312190192631373c6c8ed277254aa6e6084b74bdd0a6d3b221608e", size = 6999256, upload-time = "2025-10-15T18:22:02.617Z" }, + { url = "https://files.pythonhosted.org/packages/ba/f1/9197c9c2d5708b785f631a6dfbfa8eb3fb9672837cb92ae9af812c13b4ed/pillow-12.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:759de84a33be3b178a64c8ba28ad5c135900359e85fb662bc6e403ad4407791d", size = 2436025, upload-time = "2025-10-15T18:22:04.598Z" }, + { url = "https://files.pythonhosted.org/packages/1d/b3/582327e6c9f86d037b63beebe981425d6811104cb443e8193824ef1a2f27/pillow-12.0.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b22bd8c974942477156be55a768f7aa37c46904c175be4e158b6a86e3a6b7ca8", size = 5215068, upload-time = "2025-10-15T18:23:59.594Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d6/67748211d119f3b6540baf90f92fae73ae51d5217b171b0e8b5f7e5d558f/pillow-12.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:805ebf596939e48dbb2e4922a1d3852cfc25c38160751ce02da93058b48d252a", size = 4614994, upload-time = "2025-10-15T18:24:01.669Z" }, + { url = "https://files.pythonhosted.org/packages/2d/e1/f8281e5d844c41872b273b9f2c34a4bf64ca08905668c8ae730eedc7c9fa/pillow-12.0.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cae81479f77420d217def5f54b5b9d279804d17e982e0f2fa19b1d1e14ab5197", size = 5246639, upload-time = "2025-10-15T18:24:03.403Z" }, + { url = "https://files.pythonhosted.org/packages/94/5a/0d8ab8ffe8a102ff5df60d0de5af309015163bf710c7bb3e8311dd3b3ad0/pillow-12.0.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aeaefa96c768fc66818730b952a862235d68825c178f1b3ffd4efd7ad2edcb7c", size = 6986839, upload-time = "2025-10-15T18:24:05.344Z" }, + { url = "https://files.pythonhosted.org/packages/20/2e/3434380e8110b76cd9eb00a363c484b050f949b4bbe84ba770bb8508a02c/pillow-12.0.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09f2d0abef9e4e2f349305a4f8cc784a8a6c2f58a8c4892eea13b10a943bd26e", size = 5313505, upload-time = "2025-10-15T18:24:07.137Z" }, + { url = "https://files.pythonhosted.org/packages/57/ca/5a9d38900d9d74785141d6580950fe705de68af735ff6e727cb911b64740/pillow-12.0.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bdee52571a343d721fb2eb3b090a82d959ff37fc631e3f70422e0c2e029f3e76", size = 5963654, upload-time = "2025-10-15T18:24:09.579Z" }, + { url = "https://files.pythonhosted.org/packages/95/7e/f896623c3c635a90537ac093c6a618ebe1a90d87206e42309cb5d98a1b9e/pillow-12.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:b290fd8aa38422444d4b50d579de197557f182ef1068b75f5aa8558638b8d0a5", size = 6997850, upload-time = "2025-10-15T18:24:11.495Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/61/33/9611380c2bdb1225fdef633e2a9610622310fed35ab11dac9620972ee088/platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312", size = 21632, upload-time = "2025-10-08T17:44:48.791Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3", size = 18651, upload-time = "2025-10-08T17:44:47.223Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.52" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, +] + +[[package]] +name = "propcache" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/da/e9fc233cf63743258bff22b3dfa7ea5baef7b5bc324af47a0ad89b8ffc6f/propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d", size = 46442, upload-time = "2025-10-08T19:49:02.291Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8c/d4/4e2c9aaf7ac2242b9358f98dccd8f90f2605402f5afeff6c578682c2c491/propcache-0.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:60a8fda9644b7dfd5dece8c61d8a85e271cb958075bfc4e01083c148b61a7caf", size = 80208, upload-time = "2025-10-08T19:46:24.597Z" }, + { url = "https://files.pythonhosted.org/packages/c2/21/d7b68e911f9c8e18e4ae43bdbc1e1e9bbd971f8866eb81608947b6f585ff/propcache-0.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c30b53e7e6bda1d547cabb47c825f3843a0a1a42b0496087bb58d8fedf9f41b5", size = 45777, upload-time = "2025-10-08T19:46:25.733Z" }, + { url = "https://files.pythonhosted.org/packages/d3/1d/11605e99ac8ea9435651ee71ab4cb4bf03f0949586246476a25aadfec54a/propcache-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6918ecbd897443087a3b7cd978d56546a812517dcaaca51b49526720571fa93e", size = 47647, upload-time = "2025-10-08T19:46:27.304Z" }, + { url = "https://files.pythonhosted.org/packages/58/1a/3c62c127a8466c9c843bccb503d40a273e5cc69838805f322e2826509e0d/propcache-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d902a36df4e5989763425a8ab9e98cd8ad5c52c823b34ee7ef307fd50582566", size = 214929, upload-time = "2025-10-08T19:46:28.62Z" }, + { url = "https://files.pythonhosted.org/packages/56/b9/8fa98f850960b367c4b8fe0592e7fc341daa7a9462e925228f10a60cf74f/propcache-0.4.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a9695397f85973bb40427dedddf70d8dc4a44b22f1650dd4af9eedf443d45165", size = 221778, upload-time = "2025-10-08T19:46:30.358Z" }, + { url = "https://files.pythonhosted.org/packages/46/a6/0ab4f660eb59649d14b3d3d65c439421cf2f87fe5dd68591cbe3c1e78a89/propcache-0.4.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2bb07ffd7eaad486576430c89f9b215f9e4be68c4866a96e97db9e97fead85dc", size = 228144, upload-time = "2025-10-08T19:46:32.607Z" }, + { url = "https://files.pythonhosted.org/packages/52/6a/57f43e054fb3d3a56ac9fc532bc684fc6169a26c75c353e65425b3e56eef/propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd6f30fdcf9ae2a70abd34da54f18da086160e4d7d9251f81f3da0ff84fc5a48", size = 210030, upload-time = "2025-10-08T19:46:33.969Z" }, + { url = "https://files.pythonhosted.org/packages/40/e2/27e6feebb5f6b8408fa29f5efbb765cd54c153ac77314d27e457a3e993b7/propcache-0.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fc38cba02d1acba4e2869eef1a57a43dfbd3d49a59bf90dda7444ec2be6a5570", size = 208252, upload-time = "2025-10-08T19:46:35.309Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f8/91c27b22ccda1dbc7967f921c42825564fa5336a01ecd72eb78a9f4f53c2/propcache-0.4.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:67fad6162281e80e882fb3ec355398cf72864a54069d060321f6cd0ade95fe85", size = 202064, upload-time = "2025-10-08T19:46:36.993Z" }, + { url = "https://files.pythonhosted.org/packages/f2/26/7f00bd6bd1adba5aafe5f4a66390f243acab58eab24ff1a08bebb2ef9d40/propcache-0.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f10207adf04d08bec185bae14d9606a1444715bc99180f9331c9c02093e1959e", size = 212429, upload-time = "2025-10-08T19:46:38.398Z" }, + { url = "https://files.pythonhosted.org/packages/84/89/fd108ba7815c1117ddca79c228f3f8a15fc82a73bca8b142eb5de13b2785/propcache-0.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e9b0d8d0845bbc4cfcdcbcdbf5086886bc8157aa963c31c777ceff7846c77757", size = 216727, upload-time = "2025-10-08T19:46:39.732Z" }, + { url = "https://files.pythonhosted.org/packages/79/37/3ec3f7e3173e73f1d600495d8b545b53802cbf35506e5732dd8578db3724/propcache-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:981333cb2f4c1896a12f4ab92a9cc8f09ea664e9b7dbdc4eff74627af3a11c0f", size = 205097, upload-time = "2025-10-08T19:46:41.025Z" }, + { url = "https://files.pythonhosted.org/packages/61/b0/b2631c19793f869d35f47d5a3a56fb19e9160d3c119f15ac7344fc3ccae7/propcache-0.4.1-cp311-cp311-win32.whl", hash = "sha256:f1d2f90aeec838a52f1c1a32fe9a619fefd5e411721a9117fbf82aea638fe8a1", size = 38084, upload-time = "2025-10-08T19:46:42.693Z" }, + { url = "https://files.pythonhosted.org/packages/f4/78/6cce448e2098e9f3bfc91bb877f06aa24b6ccace872e39c53b2f707c4648/propcache-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:364426a62660f3f699949ac8c621aad6977be7126c5807ce48c0aeb8e7333ea6", size = 41637, upload-time = "2025-10-08T19:46:43.778Z" }, + { url = "https://files.pythonhosted.org/packages/9c/e9/754f180cccd7f51a39913782c74717c581b9cc8177ad0e949f4d51812383/propcache-0.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:e53f3a38d3510c11953f3e6a33f205c6d1b001129f972805ca9b42fc308bc239", size = 38064, upload-time = "2025-10-08T19:46:44.872Z" }, + { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" }, +] + +[[package]] +name = "psutil" +version = "7.1.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/88/bdd0a41e5857d5d703287598cbf08dad90aed56774ea52ae071bae9071b6/psutil-7.1.3.tar.gz", hash = "sha256:6c86281738d77335af7aec228328e944b30930899ea760ecf33a4dba66be5e74", size = 489059, upload-time = "2025-11-02T12:25:54.619Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/94/46b9154a800253e7ecff5aaacdf8ebf43db99de4a2dfa18575b02548654e/psutil-7.1.3-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2bdbcd0e58ca14996a42adf3621a6244f1bb2e2e528886959c72cf1e326677ab", size = 238359, upload-time = "2025-11-02T12:26:25.284Z" }, + { url = "https://files.pythonhosted.org/packages/68/3a/9f93cff5c025029a36d9a92fef47220ab4692ee7f2be0fba9f92813d0cb8/psutil-7.1.3-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:bc31fa00f1fbc3c3802141eede66f3a2d51d89716a194bf2cd6fc68310a19880", size = 239171, upload-time = "2025-11-02T12:26:27.23Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b1/5f49af514f76431ba4eea935b8ad3725cdeb397e9245ab919dbc1d1dc20f/psutil-7.1.3-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3bb428f9f05c1225a558f53e30ccbad9930b11c3fc206836242de1091d3e7dd3", size = 263261, upload-time = "2025-11-02T12:26:29.48Z" }, + { url = "https://files.pythonhosted.org/packages/e0/95/992c8816a74016eb095e73585d747e0a8ea21a061ed3689474fabb29a395/psutil-7.1.3-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56d974e02ca2c8eb4812c3f76c30e28836fffc311d55d979f1465c1feeb2b68b", size = 264635, upload-time = "2025-11-02T12:26:31.74Z" }, + { url = "https://files.pythonhosted.org/packages/55/4c/c3ed1a622b6ae2fd3c945a366e64eb35247a31e4db16cf5095e269e8eb3c/psutil-7.1.3-cp37-abi3-win_amd64.whl", hash = "sha256:f39c2c19fe824b47484b96f9692932248a54c43799a84282cfe58d05a6449efd", size = 247633, upload-time = "2025-11-02T12:26:33.887Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ad/33b2ccec09bf96c2b2ef3f9a6f66baac8253d7565d8839e024a6b905d45d/psutil-7.1.3-cp37-abi3-win_arm64.whl", hash = "sha256:bd0d69cee829226a761e92f28140bec9a5ee9d5b4fb4b0cc589068dbfff559b1", size = 244608, upload-time = "2025-11-02T12:26:36.136Z" }, +] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762, upload-time = "2020-12-28T15:15:30.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" }, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752, upload-time = "2024-07-21T12:58:21.801Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, +] + +[[package]] +name = "pycparser" +version = "2.23" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pyparsing" +version = "3.2.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/a5/181488fc2b9d093e3972d2a472855aae8a03f000592dbfce716a512b3359/pyparsing-3.2.5.tar.gz", hash = "sha256:2df8d5b7b2802ef88e8d016a2eb9c7aeaa923529cd251ed0fe4608275d4105b6", size = 1099274, upload-time = "2025-09-21T04:11:06.277Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl", hash = "sha256:e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e", size = 113890, upload-time = "2025-09-21T04:11:04.117Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "pytorch-lightning" +version = "2.5.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fsspec", extra = ["http"] }, + { name = "lightning-utilities" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "torch" }, + { name = "torchmetrics" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/16/78/bce84aab9a5b3b2e9d087d4f1a6be9b481adbfaac4903bc9daaaf09d49a3/pytorch_lightning-2.5.5.tar.gz", hash = "sha256:d6fc8173d1d6e49abfd16855ea05d2eb2415e68593f33d43e59028ecb4e64087", size = 643703, upload-time = "2025-09-05T16:01:18.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/f6/99a5c66478f469598dee25b0e29b302b5bddd4e03ed0da79608ac964056e/pytorch_lightning-2.5.5-py3-none-any.whl", hash = "sha256:0b533991df2353c0c6ea9ca10a7d0728b73631fd61f5a15511b19bee2aef8af0", size = 832431, upload-time = "2025-09-05T16:01:16.234Z" }, +] + +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, +] + +[[package]] +name = "pyzmq" +version = "27.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "implementation_name == 'pypy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/0b/3c9baedbdf613ecaa7aa07027780b8867f57b6293b6ee50de316c9f3222b/pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540", size = 281750, upload-time = "2025-09-08T23:10:18.157Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/5d/305323ba86b284e6fcb0d842d6adaa2999035f70f8c38a9b6d21ad28c3d4/pyzmq-27.1.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:226b091818d461a3bef763805e75685e478ac17e9008f49fce2d3e52b3d58b86", size = 1333328, upload-time = "2025-09-08T23:07:45.946Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a0/fc7e78a23748ad5443ac3275943457e8452da67fda347e05260261108cbc/pyzmq-27.1.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0790a0161c281ca9723f804871b4027f2e8b5a528d357c8952d08cd1a9c15581", size = 908803, upload-time = "2025-09-08T23:07:47.551Z" }, + { url = "https://files.pythonhosted.org/packages/7e/22/37d15eb05f3bdfa4abea6f6d96eb3bb58585fbd3e4e0ded4e743bc650c97/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c895a6f35476b0c3a54e3eb6ccf41bf3018de937016e6e18748317f25d4e925f", size = 668836, upload-time = "2025-09-08T23:07:49.436Z" }, + { url = "https://files.pythonhosted.org/packages/b1/c4/2a6fe5111a01005fc7af3878259ce17684fabb8852815eda6225620f3c59/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bbf8d3630bf96550b3be8e1fc0fea5cbdc8d5466c1192887bd94869da17a63e", size = 857038, upload-time = "2025-09-08T23:07:51.234Z" }, + { url = "https://files.pythonhosted.org/packages/cb/eb/bfdcb41d0db9cd233d6fb22dc131583774135505ada800ebf14dfb0a7c40/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:15c8bd0fe0dabf808e2d7a681398c4e5ded70a551ab47482067a572c054c8e2e", size = 1657531, upload-time = "2025-09-08T23:07:52.795Z" }, + { url = "https://files.pythonhosted.org/packages/ab/21/e3180ca269ed4a0de5c34417dfe71a8ae80421198be83ee619a8a485b0c7/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bafcb3dd171b4ae9f19ee6380dfc71ce0390fefaf26b504c0e5f628d7c8c54f2", size = 2034786, upload-time = "2025-09-08T23:07:55.047Z" }, + { url = "https://files.pythonhosted.org/packages/3b/b1/5e21d0b517434b7f33588ff76c177c5a167858cc38ef740608898cd329f2/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e829529fcaa09937189178115c49c504e69289abd39967cd8a4c215761373394", size = 1894220, upload-time = "2025-09-08T23:07:57.172Z" }, + { url = "https://files.pythonhosted.org/packages/03/f2/44913a6ff6941905efc24a1acf3d3cb6146b636c546c7406c38c49c403d4/pyzmq-27.1.0-cp311-cp311-win32.whl", hash = "sha256:6df079c47d5902af6db298ec92151db82ecb557af663098b92f2508c398bb54f", size = 567155, upload-time = "2025-09-08T23:07:59.05Z" }, + { url = "https://files.pythonhosted.org/packages/23/6d/d8d92a0eb270a925c9b4dd039c0b4dc10abc2fcbc48331788824ef113935/pyzmq-27.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:190cbf120fbc0fc4957b56866830def56628934a9d112aec0e2507aa6a032b97", size = 633428, upload-time = "2025-09-08T23:08:00.663Z" }, + { url = "https://files.pythonhosted.org/packages/ae/14/01afebc96c5abbbd713ecfc7469cfb1bc801c819a74ed5c9fad9a48801cb/pyzmq-27.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:eca6b47df11a132d1745eb3b5b5e557a7dae2c303277aa0e69c6ba91b8736e07", size = 559497, upload-time = "2025-09-08T23:08:02.15Z" }, + { url = "https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc", size = 1306279, upload-time = "2025-09-08T23:08:03.807Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5e/c3c49fdd0f535ef45eefcc16934648e9e59dace4a37ee88fc53f6cd8e641/pyzmq-27.1.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1c179799b118e554b66da67d88ed66cd37a169f1f23b5d9f0a231b4e8d44a113", size = 895645, upload-time = "2025-09-08T23:08:05.301Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e5/b0b2504cb4e903a74dcf1ebae157f9e20ebb6ea76095f6cfffea28c42ecd/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3837439b7f99e60312f0c926a6ad437b067356dc2bc2ec96eb395fd0fe804233", size = 652574, upload-time = "2025-09-08T23:08:06.828Z" }, + { url = "https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43ad9a73e3da1fab5b0e7e13402f0b2fb934ae1c876c51d0afff0e7c052eca31", size = 840995, upload-time = "2025-09-08T23:08:08.396Z" }, + { url = "https://files.pythonhosted.org/packages/c2/bb/b79798ca177b9eb0825b4c9998c6af8cd2a7f15a6a1a4272c1d1a21d382f/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0de3028d69d4cdc475bfe47a6128eb38d8bc0e8f4d69646adfbcd840facbac28", size = 1642070, upload-time = "2025-09-08T23:08:09.989Z" }, + { url = "https://files.pythonhosted.org/packages/9c/80/2df2e7977c4ede24c79ae39dcef3899bfc5f34d1ca7a5b24f182c9b7a9ca/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:cf44a7763aea9298c0aa7dbf859f87ed7012de8bda0f3977b6fb1d96745df856", size = 2021121, upload-time = "2025-09-08T23:08:11.907Z" }, + { url = "https://files.pythonhosted.org/packages/46/bd/2d45ad24f5f5ae7e8d01525eb76786fa7557136555cac7d929880519e33a/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f30f395a9e6fbca195400ce833c731e7b64c3919aa481af4d88c3759e0cb7496", size = 1878550, upload-time = "2025-09-08T23:08:13.513Z" }, + { url = "https://files.pythonhosted.org/packages/e6/2f/104c0a3c778d7c2ab8190e9db4f62f0b6957b53c9d87db77c284b69f33ea/pyzmq-27.1.0-cp312-abi3-win32.whl", hash = "sha256:250e5436a4ba13885494412b3da5d518cd0d3a278a1ae640e113c073a5f88edd", size = 559184, upload-time = "2025-09-08T23:08:15.163Z" }, + { url = "https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf", size = 619480, upload-time = "2025-09-08T23:08:17.192Z" }, + { url = "https://files.pythonhosted.org/packages/78/c2/c012beae5f76b72f007a9e91ee9401cb88c51d0f83c6257a03e785c81cc2/pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f", size = 552993, upload-time = "2025-09-08T23:08:18.926Z" }, + { url = "https://files.pythonhosted.org/packages/4c/c6/c4dcdecdbaa70969ee1fdced6d7b8f60cfabe64d25361f27ac4665a70620/pyzmq-27.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:18770c8d3563715387139060d37859c02ce40718d1faf299abddcdcc6a649066", size = 836265, upload-time = "2025-09-08T23:09:49.376Z" }, + { url = "https://files.pythonhosted.org/packages/3e/79/f38c92eeaeb03a2ccc2ba9866f0439593bb08c5e3b714ac1d553e5c96e25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:ac25465d42f92e990f8d8b0546b01c391ad431c3bf447683fdc40565941d0604", size = 800208, upload-time = "2025-09-08T23:09:51.073Z" }, + { url = "https://files.pythonhosted.org/packages/49/0e/3f0d0d335c6b3abb9b7b723776d0b21fa7f3a6c819a0db6097059aada160/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53b40f8ae006f2734ee7608d59ed661419f087521edbfc2149c3932e9c14808c", size = 567747, upload-time = "2025-09-08T23:09:52.698Z" }, + { url = "https://files.pythonhosted.org/packages/a1/cf/f2b3784d536250ffd4be70e049f3b60981235d70c6e8ce7e3ef21e1adb25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f605d884e7c8be8fe1aa94e0a783bf3f591b84c24e4bc4f3e7564c82ac25e271", size = 747371, upload-time = "2025-09-08T23:09:54.563Z" }, + { url = "https://files.pythonhosted.org/packages/01/1b/5dbe84eefc86f48473947e2f41711aded97eecef1231f4558f1f02713c12/pyzmq-27.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c9f7f6e13dff2e44a6afeaf2cf54cee5929ad64afaf4d40b50f93c58fc687355", size = 544862, upload-time = "2025-09-08T23:09:56.509Z" }, +] + +[[package]] +name = "reportlab" +version = "4.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "charset-normalizer" }, + { name = "pillow" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/fa/ed71f3e750afb77497641eb0194aeda069e271ce6d6931140f8787e0e69a/reportlab-4.4.4.tar.gz", hash = "sha256:cb2f658b7f4a15be2cc68f7203aa67faef67213edd4f2d4bdd3eb20dab75a80d", size = 3711935, upload-time = "2025-09-19T10:43:36.502Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/66/e040586fe6f9ae7f3a6986186653791fb865947f0b745290ee4ab026b834/reportlab-4.4.4-py3-none-any.whl", hash = "sha256:299b3b0534e7202bb94ed2ddcd7179b818dcda7de9d8518a57c85a58a1ebaadb", size = 1954981, upload-time = "2025-09-19T10:43:33.589Z" }, +] + +[[package]] +name = "scikit-learn" +version = "1.7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "joblib" }, + { name = "numpy" }, + { name = "scipy" }, + { name = "threadpoolctl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/c2/a7855e41c9d285dfe86dc50b250978105dce513d6e459ea66a6aeb0e1e0c/scikit_learn-1.7.2.tar.gz", hash = "sha256:20e9e49ecd130598f1ca38a1d85090e1a600147b9c02fa6f15d69cb53d968fda", size = 7193136, upload-time = "2025-09-09T08:21:29.075Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/83/564e141eef908a5863a54da8ca342a137f45a0bfb71d1d79704c9894c9d1/scikit_learn-1.7.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7509693451651cd7361d30ce4e86a1347493554f172b1c72a39300fa2aea79e", size = 9331967, upload-time = "2025-09-09T08:20:32.421Z" }, + { url = "https://files.pythonhosted.org/packages/18/d6/ba863a4171ac9d7314c4d3fc251f015704a2caeee41ced89f321c049ed83/scikit_learn-1.7.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:0486c8f827c2e7b64837c731c8feff72c0bd2b998067a8a9cbc10643c31f0fe1", size = 8648645, upload-time = "2025-09-09T08:20:34.436Z" }, + { url = "https://files.pythonhosted.org/packages/ef/0e/97dbca66347b8cf0ea8b529e6bb9367e337ba2e8be0ef5c1a545232abfde/scikit_learn-1.7.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:89877e19a80c7b11a2891a27c21c4894fb18e2c2e077815bcade10d34287b20d", size = 9715424, upload-time = "2025-09-09T08:20:36.776Z" }, + { url = "https://files.pythonhosted.org/packages/f7/32/1f3b22e3207e1d2c883a7e09abb956362e7d1bd2f14458c7de258a26ac15/scikit_learn-1.7.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8da8bf89d4d79aaec192d2bda62f9b56ae4e5b4ef93b6a56b5de4977e375c1f1", size = 9509234, upload-time = "2025-09-09T08:20:38.957Z" }, + { url = "https://files.pythonhosted.org/packages/9f/71/34ddbd21f1da67c7a768146968b4d0220ee6831e4bcbad3e03dd3eae88b6/scikit_learn-1.7.2-cp311-cp311-win_amd64.whl", hash = "sha256:9b7ed8d58725030568523e937c43e56bc01cadb478fc43c042a9aca1dacb3ba1", size = 8894244, upload-time = "2025-09-09T08:20:41.166Z" }, +] + +[[package]] +name = "scipy" +version = "1.16.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0a/ca/d8ace4f98322d01abcd52d381134344bf7b431eba7ed8b42bdea5a3c2ac9/scipy-1.16.3.tar.gz", hash = "sha256:01e87659402762f43bd2fee13370553a17ada367d42e7487800bf2916535aecb", size = 30597883, upload-time = "2025-10-28T17:38:54.068Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/5f/6f37d7439de1455ce9c5a556b8d1db0979f03a796c030bafdf08d35b7bf9/scipy-1.16.3-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:40be6cf99e68b6c4321e9f8782e7d5ff8265af28ef2cd56e9c9b2638fa08ad97", size = 36630881, upload-time = "2025-10-28T17:31:47.104Z" }, + { url = "https://files.pythonhosted.org/packages/7c/89/d70e9f628749b7e4db2aa4cd89735502ff3f08f7b9b27d2e799485987cd9/scipy-1.16.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:8be1ca9170fcb6223cc7c27f4305d680ded114a1567c0bd2bfcbf947d1b17511", size = 28941012, upload-time = "2025-10-28T17:31:53.411Z" }, + { url = "https://files.pythonhosted.org/packages/a8/a8/0e7a9a6872a923505dbdf6bb93451edcac120363131c19013044a1e7cb0c/scipy-1.16.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:bea0a62734d20d67608660f69dcda23e7f90fb4ca20974ab80b6ed40df87a005", size = 20931935, upload-time = "2025-10-28T17:31:57.361Z" }, + { url = "https://files.pythonhosted.org/packages/bd/c7/020fb72bd79ad798e4dbe53938543ecb96b3a9ac3fe274b7189e23e27353/scipy-1.16.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:2a207a6ce9c24f1951241f4693ede2d393f59c07abc159b2cb2be980820e01fb", size = 23534466, upload-time = "2025-10-28T17:32:01.875Z" }, + { url = "https://files.pythonhosted.org/packages/be/a0/668c4609ce6dbf2f948e167836ccaf897f95fb63fa231c87da7558a374cd/scipy-1.16.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:532fb5ad6a87e9e9cd9c959b106b73145a03f04c7d57ea3e6f6bb60b86ab0876", size = 33593618, upload-time = "2025-10-28T17:32:06.902Z" }, + { url = "https://files.pythonhosted.org/packages/ca/6e/8942461cf2636cdae083e3eb72622a7fbbfa5cf559c7d13ab250a5dbdc01/scipy-1.16.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0151a0749efeaaab78711c78422d413c583b8cdd2011a3c1d6c794938ee9fdb2", size = 35899798, upload-time = "2025-10-28T17:32:12.665Z" }, + { url = "https://files.pythonhosted.org/packages/79/e8/d0f33590364cdbd67f28ce79368b373889faa4ee959588beddf6daef9abe/scipy-1.16.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b7180967113560cca57418a7bc719e30366b47959dd845a93206fbed693c867e", size = 36226154, upload-time = "2025-10-28T17:32:17.961Z" }, + { url = "https://files.pythonhosted.org/packages/39/c1/1903de608c0c924a1749c590064e65810f8046e437aba6be365abc4f7557/scipy-1.16.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:deb3841c925eeddb6afc1e4e4a45e418d19ec7b87c5df177695224078e8ec733", size = 38878540, upload-time = "2025-10-28T17:32:23.907Z" }, + { url = "https://files.pythonhosted.org/packages/f1/d0/22ec7036ba0b0a35bccb7f25ab407382ed34af0b111475eb301c16f8a2e5/scipy-1.16.3-cp311-cp311-win_amd64.whl", hash = "sha256:53c3844d527213631e886621df5695d35e4f6a75f620dca412bcd292f6b87d78", size = 38722107, upload-time = "2025-10-28T17:32:29.921Z" }, + { url = "https://files.pythonhosted.org/packages/7b/60/8a00e5a524bb3bf8898db1650d350f50e6cffb9d7a491c561dc9826c7515/scipy-1.16.3-cp311-cp311-win_arm64.whl", hash = "sha256:9452781bd879b14b6f055b26643703551320aa8d79ae064a71df55c00286a184", size = 25506272, upload-time = "2025-10-28T17:32:34.577Z" }, +] + +[[package]] +name = "scs" +version = "3.2.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "scipy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0c/c0/b894547702586a252f8c417e0c77111c9d2ae1d69c4a7751eb505e4fdb62/scs-3.2.9.tar.gz", hash = "sha256:df9542d435d21938ed09494a6c525a9772779902b61300961e16890a2df7f572", size = 1690742, upload-time = "2025-10-12T20:20:21.489Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/61/7e44fe73d7cfc37c9ea35a1c3ec28f404c48ccf58bb4f22660981b696476/scs-3.2.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:af03985328c1c101ed097294ccdd9df78e4a8b76d0a335ac844df107c53ceba6", size = 95634, upload-time = "2025-10-12T20:19:29.489Z" }, + { url = "https://files.pythonhosted.org/packages/36/35/696851a343daa14a8a4d2ec142247cb61eeeed83384f4852247fa1620891/scs-3.2.9-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49b40ce47f6f0fabce13c9d9c9972b9fc58174c314a88ae2d314d7b9b713a83f", size = 5069876, upload-time = "2025-10-12T20:19:30.828Z" }, + { url = "https://files.pythonhosted.org/packages/9d/16/cfc88f0555f42ca22cacf2c960b1b1425e131be999ebd4b5e1e0550f4937/scs-3.2.9-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3476c1e6b98596f572dc48e77466013e2ca88ec391df804429fdb1317e264df2", size = 12078761, upload-time = "2025-10-12T20:19:32.658Z" }, + { url = "https://files.pythonhosted.org/packages/e5/1d/dd3d1d970b659821e643640eaff431c91027b5e75b00c10595d626d0fdeb/scs-3.2.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:be6db6874326360d82e771fbfefbc96943bdc977f29a34c89652f47d0b2dc40e", size = 11972811, upload-time = "2025-10-12T20:19:34.659Z" }, + { url = "https://files.pythonhosted.org/packages/5d/24/fa17d6a678da2cf0005029c1fe56515280f7136e92972eb14df9c57a9619/scs-3.2.9-cp311-cp311-win_amd64.whl", hash = "sha256:d56f5df1dc6b465f9ad544c6d2f83d8829da03856c9874aa81a7257f1b5a98dd", size = 7458799, upload-time = "2025-10-12T20:19:36.464Z" }, +] + +[[package]] +name = "setuptools" +version = "80.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "stack-data" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asttokens" }, + { name = "executing" }, + { name = "pure-eval" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707, upload-time = "2023-09-30T13:58:05.479Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" }, +] + +[[package]] +name = "sympy" +version = "1.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mpmath" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" }, +] + +[[package]] +name = "threadpoolctl" +version = "3.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e", size = 21274, upload-time = "2025-03-13T13:49:23.031Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638, upload-time = "2025-03-13T13:49:21.846Z" }, +] + +[[package]] +name = "torch" +version = "2.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "fsspec" }, + { name = "jinja2" }, + { name = "networkx" }, + { name = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-cupti-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-nvrtc-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-runtime-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cudnn-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cufft-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cufile-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-curand-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cusolver-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cusparse-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cusparselt-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nccl-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nvjitlink-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nvshmem-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "sympy" }, + { name = "triton", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "typing-extensions" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/fe/334225e6330e672b36aef23d77451fa906ea12881570c08638a91331a212/torch-2.9.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:c596708b5105d0b199215acf0c9be7c1db5f1680d88eddadf4b75a299259a677", size = 104230578, upload-time = "2025-10-15T15:46:08.182Z" }, + { url = "https://files.pythonhosted.org/packages/05/cc/49566caaa218872ec9a2912456f470ff92649894a4bc2e5274aa9ef87c4a/torch-2.9.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:51de31219c97c51cf4bf2be94d622e3deb5dcc526c6dc00e97c17eaec0fc1d67", size = 899815990, upload-time = "2025-10-15T15:48:03.336Z" }, + { url = "https://files.pythonhosted.org/packages/74/25/e9ab21d5925b642d008f139d4a3c9664fc9ee1faafca22913c080cc4c0a5/torch-2.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:dd515c70059afd95f48b8192733764c08ca37a1d19803af6401b5ecad7c8676e", size = 109313698, upload-time = "2025-10-15T15:46:12.425Z" }, + { url = "https://files.pythonhosted.org/packages/b3/b7/205ef3e94de636feffd64b28bb59a0dfac0771221201b9871acf9236f5ca/torch-2.9.0-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:614a185e4986326d526a91210c8fc1397e76e8cfafa78baf6296a790e53a9eec", size = 74463678, upload-time = "2025-10-15T15:46:29.779Z" }, +] + +[[package]] +name = "torchmetrics" +version = "1.8.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "lightning-utilities" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "torch" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/2e/48a887a59ecc4a10ce9e8b35b3e3c5cef29d902c4eac143378526e7485cb/torchmetrics-1.8.2.tar.gz", hash = "sha256:cf64a901036bf107f17a524009eea7781c9c5315d130713aeca5747a686fe7a5", size = 580679, upload-time = "2025-09-03T14:00:54.077Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/21/aa0f434434c48490f91b65962b1ce863fdcce63febc166ca9fe9d706c2b6/torchmetrics-1.8.2-py3-none-any.whl", hash = "sha256:08382fd96b923e39e904c4d570f3d49e2cc71ccabd2a94e0f895d1f0dac86242", size = 983161, upload-time = "2025-09-03T14:00:51.921Z" }, +] + +[[package]] +name = "tornado" +version = "6.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/09/ce/1eb500eae19f4648281bb2186927bb062d2438c2e5093d1360391afd2f90/tornado-6.5.2.tar.gz", hash = "sha256:ab53c8f9a0fa351e2c0741284e06c7a45da86afb544133201c5cc8578eb076a0", size = 510821, upload-time = "2025-08-08T18:27:00.78Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f6/48/6a7529df2c9cc12efd2e8f5dd219516184d703b34c06786809670df5b3bd/tornado-6.5.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:2436822940d37cde62771cff8774f4f00b3c8024fe482e16ca8387b8a2724db6", size = 442563, upload-time = "2025-08-08T18:26:42.945Z" }, + { url = "https://files.pythonhosted.org/packages/f2/b5/9b575a0ed3e50b00c40b08cbce82eb618229091d09f6d14bce80fc01cb0b/tornado-6.5.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:583a52c7aa94ee046854ba81d9ebb6c81ec0fd30386d96f7640c96dad45a03ef", size = 440729, upload-time = "2025-08-08T18:26:44.473Z" }, + { url = "https://files.pythonhosted.org/packages/1b/4e/619174f52b120efcf23633c817fd3fed867c30bff785e2cd5a53a70e483c/tornado-6.5.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0fe179f28d597deab2842b86ed4060deec7388f1fd9c1b4a41adf8af058907e", size = 444295, upload-time = "2025-08-08T18:26:46.021Z" }, + { url = "https://files.pythonhosted.org/packages/95/fa/87b41709552bbd393c85dd18e4e3499dcd8983f66e7972926db8d96aa065/tornado-6.5.2-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b186e85d1e3536d69583d2298423744740986018e393d0321df7340e71898882", size = 443644, upload-time = "2025-08-08T18:26:47.625Z" }, + { url = "https://files.pythonhosted.org/packages/f9/41/fb15f06e33d7430ca89420283a8762a4e6b8025b800ea51796ab5e6d9559/tornado-6.5.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e792706668c87709709c18b353da1f7662317b563ff69f00bab83595940c7108", size = 443878, upload-time = "2025-08-08T18:26:50.599Z" }, + { url = "https://files.pythonhosted.org/packages/11/92/fe6d57da897776ad2e01e279170ea8ae726755b045fe5ac73b75357a5a3f/tornado-6.5.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:06ceb1300fd70cb20e43b1ad8aaee0266e69e7ced38fa910ad2e03285009ce7c", size = 444549, upload-time = "2025-08-08T18:26:51.864Z" }, + { url = "https://files.pythonhosted.org/packages/9b/02/c8f4f6c9204526daf3d760f4aa555a7a33ad0e60843eac025ccfd6ff4a93/tornado-6.5.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:74db443e0f5251be86cbf37929f84d8c20c27a355dd452a5cfa2aada0d001ec4", size = 443973, upload-time = "2025-08-08T18:26:53.625Z" }, + { url = "https://files.pythonhosted.org/packages/ae/2d/f5f5707b655ce2317190183868cd0f6822a1121b4baeae509ceb9590d0bd/tornado-6.5.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b5e735ab2889d7ed33b32a459cac490eda71a1ba6857b0118de476ab6c366c04", size = 443954, upload-time = "2025-08-08T18:26:55.072Z" }, + { url = "https://files.pythonhosted.org/packages/e8/59/593bd0f40f7355806bf6573b47b8c22f8e1374c9b6fd03114bd6b7a3dcfd/tornado-6.5.2-cp39-abi3-win32.whl", hash = "sha256:c6f29e94d9b37a95013bb669616352ddb82e3bfe8326fccee50583caebc8a5f0", size = 445023, upload-time = "2025-08-08T18:26:56.677Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2a/f609b420c2f564a748a2d80ebfb2ee02a73ca80223af712fca591386cafb/tornado-6.5.2-cp39-abi3-win_amd64.whl", hash = "sha256:e56a5af51cc30dd2cae649429af65ca2f6571da29504a07995175df14c18f35f", size = 445427, upload-time = "2025-08-08T18:26:57.91Z" }, + { url = "https://files.pythonhosted.org/packages/5e/4f/e1f65e8f8c76d73658b33d33b81eed4322fb5085350e4328d5c956f0c8f9/tornado-6.5.2-cp39-abi3-win_arm64.whl", hash = "sha256:d6c33dc3672e3a1f3618eb63b7ef4683a7688e7b9e6e8f0d9aa5726360a004af", size = 444456, upload-time = "2025-08-08T18:26:59.207Z" }, +] + +[[package]] +name = "tqdm" +version = "4.67.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, +] + +[[package]] +name = "traitlets" +version = "5.14.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621, upload-time = "2024-04-19T11:11:49.746Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" }, +] + +[[package]] +name = "triton" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/78/949a04391c21956c816523678f0e5fa308eb5b1e7622d88c4e4ef5fceca0/triton-3.5.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f34bfa21c5b3a203c0f0eab28dcc1e49bd1f67d22724e77fb6665a659200a4ec", size = 170433488, upload-time = "2025-10-13T16:37:57.132Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "tzdata" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, +] + +[[package]] +name = "wcwidth" +version = "0.2.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/30/6b0809f4510673dc723187aeaf24c7f5459922d01e2f794277a3dfb90345/wcwidth-0.2.14.tar.gz", hash = "sha256:4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605", size = 102293, upload-time = "2025-09-22T16:29:53.023Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1", size = 37286, upload-time = "2025-09-22T16:29:51.641Z" }, +] + +[[package]] +name = "yarl" +version = "1.22.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/57/63/0c6ebca57330cd313f6102b16dd57ffaf3ec4c83403dcb45dbd15c6f3ea1/yarl-1.22.0.tar.gz", hash = "sha256:bebf8557577d4401ba8bd9ff33906f1376c877aa78d1fe216ad01b4d6745af71", size = 187169, upload-time = "2025-10-06T14:12:55.963Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/27/5ab13fc84c76a0250afd3d26d5936349a35be56ce5785447d6c423b26d92/yarl-1.22.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ab72135b1f2db3fed3997d7e7dc1b80573c67138023852b6efb336a5eae6511", size = 141607, upload-time = "2025-10-06T14:09:16.298Z" }, + { url = "https://files.pythonhosted.org/packages/6a/a1/d065d51d02dc02ce81501d476b9ed2229d9a990818332242a882d5d60340/yarl-1.22.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:669930400e375570189492dc8d8341301578e8493aec04aebc20d4717f899dd6", size = 94027, upload-time = "2025-10-06T14:09:17.786Z" }, + { url = "https://files.pythonhosted.org/packages/c1/da/8da9f6a53f67b5106ffe902c6fa0164e10398d4e150d85838b82f424072a/yarl-1.22.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:792a2af6d58177ef7c19cbf0097aba92ca1b9cb3ffdd9c7470e156c8f9b5e028", size = 94963, upload-time = "2025-10-06T14:09:19.662Z" }, + { url = "https://files.pythonhosted.org/packages/68/fe/2c1f674960c376e29cb0bec1249b117d11738db92a6ccc4a530b972648db/yarl-1.22.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ea66b1c11c9150f1372f69afb6b8116f2dd7286f38e14ea71a44eee9ec51b9d", size = 368406, upload-time = "2025-10-06T14:09:21.402Z" }, + { url = "https://files.pythonhosted.org/packages/95/26/812a540e1c3c6418fec60e9bbd38e871eaba9545e94fa5eff8f4a8e28e1e/yarl-1.22.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3e2daa88dc91870215961e96a039ec73e4937da13cf77ce17f9cad0c18df3503", size = 336581, upload-time = "2025-10-06T14:09:22.98Z" }, + { url = "https://files.pythonhosted.org/packages/0b/f5/5777b19e26fdf98563985e481f8be3d8a39f8734147a6ebf459d0dab5a6b/yarl-1.22.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba440ae430c00eee41509353628600212112cd5018d5def7e9b05ea7ac34eb65", size = 388924, upload-time = "2025-10-06T14:09:24.655Z" }, + { url = "https://files.pythonhosted.org/packages/86/08/24bd2477bd59c0bbd994fe1d93b126e0472e4e3df5a96a277b0a55309e89/yarl-1.22.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e6438cc8f23a9c1478633d216b16104a586b9761db62bfacb6425bac0a36679e", size = 392890, upload-time = "2025-10-06T14:09:26.617Z" }, + { url = "https://files.pythonhosted.org/packages/46/00/71b90ed48e895667ecfb1eaab27c1523ee2fa217433ed77a73b13205ca4b/yarl-1.22.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c52a6e78aef5cf47a98ef8e934755abf53953379b7d53e68b15ff4420e6683d", size = 365819, upload-time = "2025-10-06T14:09:28.544Z" }, + { url = "https://files.pythonhosted.org/packages/30/2d/f715501cae832651d3282387c6a9236cd26bd00d0ff1e404b3dc52447884/yarl-1.22.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3b06bcadaac49c70f4c88af4ffcfbe3dc155aab3163e75777818092478bcbbe7", size = 363601, upload-time = "2025-10-06T14:09:30.568Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f9/a678c992d78e394e7126ee0b0e4e71bd2775e4334d00a9278c06a6cce96a/yarl-1.22.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:6944b2dc72c4d7f7052683487e3677456050ff77fcf5e6204e98caf785ad1967", size = 358072, upload-time = "2025-10-06T14:09:32.528Z" }, + { url = "https://files.pythonhosted.org/packages/2c/d1/b49454411a60edb6fefdcad4f8e6dbba7d8019e3a508a1c5836cba6d0781/yarl-1.22.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d5372ca1df0f91a86b047d1277c2aaf1edb32d78bbcefffc81b40ffd18f027ed", size = 385311, upload-time = "2025-10-06T14:09:34.634Z" }, + { url = "https://files.pythonhosted.org/packages/87/e5/40d7a94debb8448c7771a916d1861d6609dddf7958dc381117e7ba36d9e8/yarl-1.22.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:51af598701f5299012b8416486b40fceef8c26fc87dc6d7d1f6fc30609ea0aa6", size = 381094, upload-time = "2025-10-06T14:09:36.268Z" }, + { url = "https://files.pythonhosted.org/packages/35/d8/611cc282502381ad855448643e1ad0538957fc82ae83dfe7762c14069e14/yarl-1.22.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b266bd01fedeffeeac01a79ae181719ff848a5a13ce10075adbefc8f1daee70e", size = 370944, upload-time = "2025-10-06T14:09:37.872Z" }, + { url = "https://files.pythonhosted.org/packages/2d/df/fadd00fb1c90e1a5a8bd731fa3d3de2e165e5a3666a095b04e31b04d9cb6/yarl-1.22.0-cp311-cp311-win32.whl", hash = "sha256:a9b1ba5610a4e20f655258d5a1fdc7ebe3d837bb0e45b581398b99eb98b1f5ca", size = 81804, upload-time = "2025-10-06T14:09:39.359Z" }, + { url = "https://files.pythonhosted.org/packages/b5/f7/149bb6f45f267cb5c074ac40c01c6b3ea6d8a620d34b337f6321928a1b4d/yarl-1.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:078278b9b0b11568937d9509b589ee83ef98ed6d561dfe2020e24a9fd08eaa2b", size = 86858, upload-time = "2025-10-06T14:09:41.068Z" }, + { url = "https://files.pythonhosted.org/packages/2b/13/88b78b93ad3f2f0b78e13bfaaa24d11cbc746e93fe76d8c06bf139615646/yarl-1.22.0-cp311-cp311-win_arm64.whl", hash = "sha256:b6a6f620cfe13ccec221fa312139135166e47ae169f8253f72a0abc0dae94376", size = 81637, upload-time = "2025-10-06T14:09:42.712Z" }, + { url = "https://files.pythonhosted.org/packages/73/ae/b48f95715333080afb75a4504487cbe142cae1268afc482d06692d605ae6/yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff", size = 46814, upload-time = "2025-10-06T14:12:53.872Z" }, +]