This commit is contained in:
2025-12-12 13:42:41 -06:00
parent e5f479e266
commit a20f560cd7
8 changed files with 1735 additions and 465 deletions

View File

@@ -10,7 +10,7 @@
},
{
"cell_type": "code",
"execution_count": 30,
"execution_count": 2,
"id": "e4ff9106",
"metadata": {},
"outputs": [],
@@ -24,7 +24,7 @@
},
{
"cell_type": "code",
"execution_count": 31,
"execution_count": 4,
"id": "32946653",
"metadata": {},
"outputs": [
@@ -47,60 +47,6 @@
"memory usage: 209.7 KB\n",
"\n",
"After adding mirrored data:\n",
"Total rows: 8281\n",
"Roll angle range: -4.0° to 4.0°\n",
"Unique roll angles: [np.float64(-4.0), np.float64(-3.0), np.float64(-2.0), np.float64(-1.5), np.float64(-1.0), np.float64(-0.5), np.float64(0.0), np.float64(0.5), np.float64(1.0), np.float64(1.5), np.float64(2.0), np.float64(3.0), np.float64(4.0)]\n"
]
}
],
"source": [
"magDf = pd.read_csv(\"Ansys Results 12-9.csv\")\n",
"magDf.info()\n",
"\n",
"# Condition: Drop rows where 'GapHeight' is less than 5\n",
"condition = magDf['GapHeight [mm]'] < 5\n",
"\n",
"# Drop the rows that satisfy the condition (i.e., keep rows where the condition is False)\n",
"magDf = magDf[~condition]\n",
"\n",
"# Create mirrored data with negative roll angles\n",
"# For non-zero roll angles, create symmetric data by:\n",
"# - Negating roll angle\n",
"# - Swapping currL and currR\n",
"# - Negating torque (force remains the same due to symmetry)\n",
"\n",
"# Filter for non-zero roll angles\n",
"non_zero_roll = magDf[magDf['rollDeg [deg]'] != 0]\n",
"\n",
"# Create the mirrored rows\n",
"mirrored_data = non_zero_roll.copy()\n",
"mirrored_data['rollDeg [deg]'] = -non_zero_roll['rollDeg [deg]']\n",
"mirrored_data['currL [A]'], mirrored_data['currR [A]'] = non_zero_roll['currR [A]'].values, non_zero_roll['currL [A]'].values\n",
"mirrored_data['YokeTorque.Torque [mNewtonMeter]'] = -non_zero_roll['YokeTorque.Torque [mNewtonMeter]']\n",
"# Force remains unchanged\n",
"\n",
"# Combine original and mirrored data\n",
"magDf = pd.concat([magDf, mirrored_data], ignore_index=True)\n",
"\n",
"# Sort by rollDeg for better organization\n",
"magDf = magDf.sort_values(['rollDeg [deg]', 'currL [A]', 'currR [A]', 'GapHeight [mm]']).reset_index(drop=True)\n",
"\n",
"print(f\"\\nAfter adding mirrored data:\")\n",
"print(f\"Total rows: {len(magDf)}\")\n",
"print(f\"Roll angle range: {magDf['rollDeg [deg]'].min():.1f}° to {magDf['rollDeg [deg]'].max():.1f}°\")\n",
"print(f\"Unique roll angles: {sorted(magDf['rollDeg [deg]'].unique())}\")"
]
},
{
"cell_type": "code",
"execution_count": 32,
"id": "4fe774dd",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"<class 'pandas.core.frame.DataFrame'>\n",
"RangeIndex: 8281 entries, 0 to 8280\n",
"Data columns (total 6 columns):\n",
@@ -118,6 +64,26 @@
}
],
"source": [
"magDf = pd.read_csv(\"Ansys Results 12-9.csv\")\n",
"magDf.info()\n",
"\n",
"condition = magDf['GapHeight [mm]'] < 5\n",
"\n",
"magDf = magDf[~condition]\n",
"\n",
"nzRoll = magDf[magDf['rollDeg [deg]'] != 0]\n",
"\n",
"mirrored_data = nzRoll.copy()\n",
"mirrored_data['rollDeg [deg]'] = -nzRoll['rollDeg [deg]']\n",
"mirrored_data['currL [A]'], mirrored_data['currR [A]'] = nzRoll['currR [A]'].values, nzRoll['currL [A]'].values\n",
"mirrored_data['YokeTorque.Torque [mNewtonMeter]'] = -nzRoll['YokeTorque.Torque [mNewtonMeter]']\n",
"\n",
"magDf = pd.concat([magDf, mirrored_data], ignore_index=True)\n",
"\n",
"magDf = magDf.sort_values(['rollDeg [deg]', 'currL [A]', 'currR [A]', 'GapHeight [mm]']).reset_index(drop=True)\n",
"\n",
"print()\n",
"print(\"After adding mirrored data:\")\n",
"magDf.info()"
]
},
@@ -126,12 +92,12 @@
"id": "ecd1f124",
"metadata": {},
"source": [
"### After removing non-model rows, we have 4459 rows, which matches the number of simulations conducted in Ansys."
"### After removing non-model rows, we have 4459 rows * ~2 for the mirrored roll angles, which matches the number of simulations conducted in Ansys."
]
},
{
"cell_type": "code",
"execution_count": 33,
"execution_count": null,
"id": "1a76017e",
"metadata": {},
"outputs": [
@@ -155,7 +121,9 @@
"plt.title(r\"$-15A, -15A, 0^\\circ roll$\")\n",
"plt.xlabel(\"Inverse Gap Height (1/mm)\")\n",
"plt.ylabel(\"Force on magnetic yoke (N)\")\n",
"plt.show()"
"plt.show()\n",
"\n",
"# Experimented here with ways to represent gap-height-to-force relationship."
]
},
{
@@ -307,7 +275,7 @@
},
{
"cell_type": "code",
"execution_count": 35,
"execution_count": null,
"id": "bd16a120",
"metadata": {},
"outputs": [
@@ -386,6 +354,7 @@
"from sklearn.preprocessing import PolynomialFeatures\n",
"from sklearn.linear_model import LinearRegression\n",
"\n",
"# The following was written by AI - see [4]\n",
"# 1. Load your Ansys CSV - using invGap for modeling\n",
"X = magDf[['currL [A]', 'currR [A]', 'rollDeg [deg]', 'invGap']]\n",
"y = magDf[['YokeForce.Force_z [newton]', 'YokeTorque.Torque [mNewtonMeter]']]\n",
@@ -413,7 +382,7 @@
},
{
"cell_type": "code",
"execution_count": 36,
"execution_count": null,
"id": "dc79d9fe",
"metadata": {},
"outputs": [
@@ -437,21 +406,17 @@
}
],
"source": [
"# Get predictions from the model\n",
"y_pred = model.predict(X_poly)\n",
"\n",
"# Extract force and torque predictions\n",
"force_pred = y_pred[:, 0]\n",
"torque_pred = y_pred[:, 1]\n",
"\n",
"# Extract actual values\n",
"force_actual = y.iloc[:, 0].values\n",
"torque_actual = y.iloc[:, 1].values\n",
"\n",
"# Create comparison plots\n",
"fig, axes = plt.subplots(1, 2, figsize=(14, 5))\n",
"\n",
"# Plot 1: Force comparison\n",
"# Force comparison\n",
"axes[0].scatter(force_actual, force_pred, alpha=0.5, s=10)\n",
"axes[0].plot([force_actual.min(), force_actual.max()], \n",
" [force_actual.min(), force_actual.max()], \n",
@@ -462,7 +427,7 @@
"axes[0].legend()\n",
"axes[0].grid(True, alpha=0.3)\n",
"\n",
"# Plot 2: Torque comparison\n",
"# Torque comparison\n",
"axes[1].scatter(torque_actual, torque_pred, alpha=0.5, s=10)\n",
"axes[1].plot([torque_actual.min(), torque_actual.max()], \n",
" [torque_actual.min(), torque_actual.max()], \n",
@@ -476,7 +441,6 @@
"plt.tight_layout()\n",
"plt.show()\n",
"\n",
"# Calculate R² scores\n",
"from sklearn.metrics import r2_score\n",
"print(f\"Force R² Score: {r2_score(force_actual, force_pred):.6f}\")\n",
"print(f\"Torque R² Score: {r2_score(torque_actual, torque_pred):.6f}\")"
@@ -492,7 +456,7 @@
},
{
"cell_type": "code",
"execution_count": 37,
"execution_count": null,
"id": "837a814f",
"metadata": {},
"outputs": [
@@ -508,7 +472,7 @@
}
],
"source": [
"# For the first subset (same as you plotted earlier)\n",
"# For the first subset\n",
"subset_indices = range(0, 13)\n",
"x_subset = magDf.iloc[subset_indices][\"GapHeight [mm]\"]\n",
"y_actual_subset = magDf.iloc[subset_indices][\"YokeForce.Force_z [newton]\"]\n",
@@ -548,7 +512,7 @@
},
{
"cell_type": "code",
"execution_count": 38,
"execution_count": null,
"id": "fd12d56b",
"metadata": {},
"outputs": [
@@ -590,7 +554,6 @@
"from sklearn.model_selection import cross_val_score\n",
"from sklearn.pipeline import Pipeline\n",
"\n",
"# Split data for proper validation\n",
"from sklearn.model_selection import train_test_split\n",
"X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)\n",
"\n",
@@ -602,26 +565,21 @@
"test_scores_torque = []\n",
"\n",
"for degree in degrees:\n",
" # Create pipeline\n",
" pipe = Pipeline([\n",
" ('poly', PolynomialFeatures(degree=degree)),\n",
" ('model', LinearRegression())\n",
" ])\n",
" \n",
" # Fit on training data\n",
" pipe.fit(X_train, y_train)\n",
" \n",
" # Score on both sets\n",
" train_score = pipe.score(X_train, y_train)\n",
" test_score = pipe.score(X_test, y_test)\n",
" \n",
" # Store scores (using overall R² for multi-output)\n",
" train_scores_force.append(train_score)\n",
" test_scores_force.append(test_score)\n",
" \n",
" print(f\"Degree {degree}: Train R² = {train_score:.6f}, Test R² = {test_score:.6f}\")\n",
"\n",
"# Plot the results\n",
"plt.figure(figsize=(10, 6))\n",
"plt.plot(degrees, train_scores_force, 'o-', label='Training Score', linewidth=2, markersize=8)\n",
"plt.plot(degrees, test_scores_force, 's-', label='Test Score', linewidth=2, markersize=8)\n",
@@ -633,7 +591,6 @@
"plt.xticks(degrees)\n",
"plt.show()\n",
"\n",
"# Find best degree\n",
"best_degree = degrees[np.argmax(test_scores_force)]\n",
"print(f\"\\nBest polynomial degree: {best_degree}\")\n",
"print(f\"Best test R² score: {max(test_scores_force):.6f}\")"
@@ -661,7 +618,7 @@
},
{
"cell_type": "code",
"execution_count": 40,
"execution_count": null,
"id": "d94aa4c1",
"metadata": {},
"outputs": [
@@ -694,6 +651,7 @@
}
],
"source": [
"# The following was written by AI - see [5]\n",
"# Train best degree polynomial on full dataset\n",
"poly_best = PolynomialFeatures(degree=best_degree)\n",
"X_poly_best = poly_best.fit_transform(X)\n",
@@ -794,6 +752,7 @@
}
],
"source": [
"# The following was written by AI - see [6]\n",
"# Select 4 different conditions to visualize\n",
"# Each condition has 13 gap height measurements (one complete parameter set)\n",
"condition_indices = [\n",
@@ -874,7 +833,7 @@
},
{
"cell_type": "code",
"execution_count": 42,
"execution_count": null,
"id": "35f4438e",
"metadata": {},
"outputs": [
@@ -896,6 +855,7 @@
}
],
"source": [
"# The following was written by AI - see [7]\n",
"# Calculate R² score for each 13-row segment (each parameter set)\n",
"num_segments = len(magDf) // 13\n",
"segment_scores = []\n",
@@ -953,7 +913,7 @@
},
{
"cell_type": "code",
"execution_count": 43,
"execution_count": null,
"id": "6ceaf2b2",
"metadata": {},
"outputs": [
@@ -977,6 +937,7 @@
}
],
"source": [
"# The following was written by AI - see [8]\n",
"# Get worst segment data\n",
"worst_data = magDf.iloc[worst_segment['start']:worst_segment['end']]\n",
"worst_currL = worst_segment['currL']\n",
@@ -1059,7 +1020,7 @@
},
{
"cell_type": "code",
"execution_count": 44,
"execution_count": null,
"id": "c120048d",
"metadata": {},
"outputs": [
@@ -1085,14 +1046,12 @@
}
],
"source": [
"# Calculate overall torque prediction accuracy\n",
"y_pred_full = model_best.predict(X_poly_best)\n",
"torque_actual_full = y.iloc[:, 1].values\n",
"torque_pred_full = y_pred_full[:, 1]\n",
"\n",
"torque_r2_overall = r2_score(torque_actual_full, torque_pred_full)\n",
"\n",
"# Create scatter plot for torque\n",
"fig, ax = plt.subplots(1, 1, figsize=(10, 8))\n",
"\n",
"ax.scatter(torque_actual_full, torque_pred_full, alpha=0.4, s=15, color='#1f77b4')\n",
@@ -1127,7 +1086,7 @@
},
{
"cell_type": "code",
"execution_count": 45,
"execution_count": null,
"id": "70b9b4e1",
"metadata": {},
"outputs": [
@@ -1151,6 +1110,7 @@
}
],
"source": [
"# The following was written by AI - see [9]\n",
"# Select 4 different gap heights to visualize\n",
"# Using currL = -15A, currR = -15A configuration\n",
"gap_heights_to_plot = [8, 10, 15, 21] # mm\n",
@@ -1227,7 +1187,7 @@
},
{
"cell_type": "code",
"execution_count": 46,
"execution_count": null,
"id": "3b8d7f6a",
"metadata": {},
"outputs": [
@@ -1253,6 +1213,7 @@
}
],
"source": [
"# The following was written by AI - see [10]\n",
"# Select 4 different gap heights to visualize\n",
"# Using roll = 0.5 degrees (small but non-zero torque)\n",
"gap_heights_to_plot = [8, 10, 15, 21] # mm\n",
@@ -1346,7 +1307,7 @@
},
{
"cell_type": "code",
"execution_count": 47,
"execution_count": null,
"id": "902343b2",
"metadata": {},
"outputs": [
@@ -1379,6 +1340,7 @@
}
],
"source": [
"# Following generated by AI - see [11]\n",
"# Calculate R² score for torque in each 13-row segment\n",
"torque_segment_scores = []\n",
"torque_segment_info = []\n",
@@ -1450,7 +1412,7 @@
},
{
"cell_type": "code",
"execution_count": 48,
"execution_count": null,
"id": "9d5580f3",
"metadata": {},
"outputs": [
@@ -1473,6 +1435,7 @@
}
],
"source": [
"# Following generated by AI - see [11]\n",
"# Get worst torque segment data\n",
"worst_torque_data = magDf.iloc[worst_torque_segment['start']:worst_torque_segment['end']]\n",
"worst_torque_currL = worst_torque_segment['currL']\n",
@@ -1551,7 +1514,7 @@
},
{
"cell_type": "code",
"execution_count": 49,
"execution_count": null,
"id": "778f58df",
"metadata": {},
"outputs": [
@@ -1590,6 +1553,7 @@
}
],
"source": [
"# The following was generated by AI - see [12]\n",
"print(\"=\" * 60)\n",
"print(\"MODEL EXPORT SUMMARY\")\n",
"print(\"=\" * 60)\n",
@@ -1626,7 +1590,7 @@
},
{
"cell_type": "code",
"execution_count": 50,
"execution_count": null,
"id": "edb239e1",
"metadata": {},
"outputs": [
@@ -1652,6 +1616,7 @@
}
],
"source": [
"# The following was generated by AI - see [12]\n",
"# Generate standalone predictor class\n",
"predictor_code = f'''\"\"\"\n",
"Magnetic Levitation Force and Torque Predictor\n",
@@ -1829,7 +1794,7 @@
},
{
"cell_type": "code",
"execution_count": 51,
"execution_count": null,
"id": "4a1ddacd",
"metadata": {},
"outputs": [
@@ -1864,6 +1829,7 @@
}
],
"source": [
"# The following was generated by AI - see [12]\n",
"# Import the standalone predictor (reload to get latest version)\n",
"import importlib\n",
"import maglev_predictor\n",
@@ -1927,15 +1893,12 @@
"source": [
"### Export Sklearn Model (Lossless & Faster)\n",
"\n",
"Save the actual sklearn model using joblib for lossless serialization. This is faster than the standalone predictor because:\n",
"- Uses optimized sklearn implementations\n",
"- No manual feature generation overhead\n",
"- Smaller memory footprint for predictions"
"Save the actual sklearn model using joblib for lossless serialization. This is also faster than the standalone predictor"
]
},
{
"cell_type": "code",
"execution_count": 52,
"execution_count": null,
"id": "5fd33470",
"metadata": {},
"outputs": [
@@ -1974,6 +1937,7 @@
}
],
"source": [
"# The following was generated by AI - see [12]\n",
"import joblib\n",
"import pickle\n",
"\n",
@@ -2030,7 +1994,7 @@
},
{
"cell_type": "code",
"execution_count": 53,
"execution_count": null,
"id": "889df820",
"metadata": {},
"outputs": [
@@ -2061,6 +2025,7 @@
}
],
"source": [
"# The following was generated by AI - see [12]\n",
"import time\n",
"\n",
"# Load the sklearn model\n",
@@ -2141,7 +2106,7 @@
},
{
"cell_type": "code",
"execution_count": 54,
"execution_count": null,
"id": "badbc379",
"metadata": {},
"outputs": [
@@ -2164,6 +2129,7 @@
}
],
"source": [
"# The following was generated by AI - see [13]\n",
"# Find equilibrium gap height for 5.8 kg pod using polynomial root finding\n",
"import numpy as np\n",
"from maglev_predictor import MaglevPredictor\n",
@@ -2220,19 +2186,11 @@
" print(f\" Gap: {gap_val:.6f} mm → Force: {force:.6f} N\")\n",
"print()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3ec4bb72",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "LevSim",
"display_name": "RLenv",
"language": "python",
"name": "python3"
},
@@ -2246,7 +2204,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.19"
"version": "3.10.9"
}
},
"nbformat": 4,