├── .gitignore ├── BH └── M-19-Steel-BH-Curve-afterJMAGsmooth.BH ├── README.md ├── clean_jfiles.py ├── clean_large_files.py ├── codes3 ├── CrossSectInnerConsequentPoleRotor.py ├── CrossSectInnerConsequentSinglePoleRotor.py ├── CrossSectInnerNotchedRotor.py ├── CrossSectInnerSalientPoleRotor.py ├── CrossSectStator.py ├── CrossSectToken.py ├── CrossSectVShapeConsequentPoleRotor.py ├── DesignParametersReproduced ├── FEMM_SlidingMesh.py ├── FEMM_Solver.py ├── JMAG.py ├── Location2D.py ├── PSWinding_CommonRing_DesignTable_Generator.py ├── ParetoFrontPlotOverlapped.py ├── Pole-specific_winding_with_neutral_plate_the_design_table_generator.py ├── Pole_specific_winding_with_neutral_plate_the_design_table_generator.py ├── Problem_BearinglessInductionDesign.py ├── Problem_BearinglessSynchronousDesign.py ├── PyX_Utility.py ├── PyX_classes.py ├── VanGogh.py ├── VanGogh_Cairo.py ├── __init__.py ├── acm_designer.py ├── acmop.py ├── angle_error_nick.py ├── bearingless_VShapeconsequentPole_design.py ├── bearingless_consequentPole_design.py ├── bearingless_consequentsinglePole_design.py ├── bearingless_heart_design.py ├── bearingless_induction_design.py ├── bearingless_spmsm_design.py ├── bearingless_spmsm_heart.py ├── flux_alternator_design.py ├── flux_switching_pm_design.py ├── initialDesignFSPM.py ├── inner_rotor_motor.py ├── machine_simulation.json ├── machine_specifications.json ├── material.py ├── method_parallel_solve_4jmag.py ├── method_parasolve_greedy_search.py ├── mop.code-workspace ├── parasolve.py ├── parasolveSlidingMesh.py ├── parasolve_greedy_search.py ├── parasolve_greedy_search_manager.py ├── pie_chart_donut_chart.py ├── population.py ├── pyfemm_script.py ├── pyrhonen_procedure_as_function.py ├── pyx_pole_specific_theory_3D_cages.py ├── pyx_pole_specific_theory_cage_fields.py ├── pyx_pole_specific_theory_cage_phasor_diagram.py ├── pyx_pole_specific_theory_cage_phasor_diagram_by_p_ps_fileds.py ├── pyx_saturation_time_correction_annotated.py ├── pyx_saturation_time_correction_annotated_extended.py ├── pyx_saturation_time_correction_annotated_ideal_situation.py ├── reduce_image_size.py ├── streamlit_user_session_data.json ├── test.py ├── utility.py ├── utility_json.py ├── utility_moo.py ├── utility_postprocess.py ├── vernier_motor_design.py ├── visualize-demo-edit-table.py ├── visualize.py ├── visualize_everything.py ├── visualize_streamlit_user_session_data.json ├── where_am_i.py ├── winding_layout.py ├── winding_layout_derivation_ismb2020_asymetry.py └── winding_layout_derivation_ismb2021_asymetry.py └── test.py /.gitignore: -------------------------------------------------------------------------------- 1 | codes3/streamlit_user_session_data.json 2 | 3 | .*/ 4 | 5 | *.code-workspace 6 | 7 | Initial 8 | 9 | streamllit_user_session_data.json 10 | *.pkl 11 | 12 | # temp 13 | Single-Layer-FSW 14 | /ACMDM_outputs 15 | /article 16 | /Arnon5 17 | /A-1D-Optimization 18 | /femm_files 19 | /DataFolder* 20 | 21 | # saved python object data file 22 | __*.json 23 | 24 | # 25 | *.tex 26 | /*#* 27 | /_* 28 | /backup* 29 | /PMSM_* 30 | /IM_* 31 | /pop 32 | /pso 33 | /release 34 | /txt_collected 35 | *HORY-Y730* 36 | *.bat 37 | *.csv 38 | *.dmp 39 | *.xls 40 | *.xlsx 41 | *.log 42 | *.lnk 43 | *.png 44 | *.pyc 45 | *.sublime* 46 | *.txt 47 | *.pdf 48 | *.aux 49 | *.dvi 50 | *.eps 51 | *.synctex* 52 | *.svg 53 | *.zip 54 | *.7z 55 | *.html 56 | *.lnk 57 | -------------------------------------------------------------------------------- /BH/M-19-Steel-BH-Curve-afterJMAGsmooth.BH: -------------------------------------------------------------------------------- 1 | 0 0 2 | 3.7801785 0.0403935025191 3 | 7.560357 0.0807694208799 4 | 11.3405355 0.121110170924 5 | 15.120714 0.161398168493 6 | 17.0201085 0.181615891011 7 | 18.919503 0.20181362516 8 | 20.8188975 0.221989140297 9 | 22.718292 0.242140205779 10 | 23.99940225 0.255716837114 11 | 25.2805125 0.269280646379 12 | 26.56162275 0.282830949122 13 | 27.842733 0.296367060889 14 | 28.84990825 0.306998386537 15 | 29.8570835 0.317620185558 16 | 30.86425875 0.328232125368 17 | 31.871434 0.338833873385 18 | 32.7448365 0.34801899152 19 | 33.618239 0.357195978422 20 | 34.4916415 0.366364617208 21 | 35.365044 0.375524690994 22 | 36.17393 0.384000300376 23 | 36.982816 0.392468205071 24 | 37.791702 0.400928232795 25 | 38.600588 0.409380211262 26 | 39.3844915 0.417563306905 27 | 40.168395 0.425738524201 28 | 40.9522985 0.43390570634 29 | 41.736202 0.442064696513 30 | 42.52064625 0.450220957444 31 | 43.3050905 0.458368700943 32 | 44.08953475 0.466507769876 33 | 44.873979 0.474638007107 34 | 45.677436 0.482955979122 35 | 46.480893 0.491264352453 36 | 47.28435 0.499562958262 37 | 48.087807 0.50785162771 38 | 48.92516425 0.516479264598 39 | 49.7625215 0.525095734434 40 | 50.59987875 0.533700846094 41 | 51.437236 0.542294408453 42 | 52.32173225 0.551358990819 43 | 53.2062285 0.560410248444 44 | 54.09072475 0.569447956075 45 | 54.975221 0.578471888456 46 | 55.919664 0.588091956422 47 | 56.864107 0.597695787578 48 | 57.80855 0.607283107695 49 | 58.752993 0.616853642546 50 | 59.77065575 0.627147048867 51 | 60.7883185 0.637420304939 52 | 61.80598125 0.64767306768 53 | 62.823644 0.657904994009 54 | 63.92905425 0.668995154771 55 | 65.0344645 0.680059886495 56 | 66.13987475 0.691098749484 57 | 67.245285 0.702111304038 58 | 68.45506525 0.714132964288 59 | 69.6648455 0.726122010601 60 | 70.87462575 0.738077866604 61 | 72.084406 0.749999955922 62 | 73.4183295 0.762973007853 63 | 74.752253 0.775634769861 64 | 76.0861765 0.787979140509 65 | 77.4201 0.800000018361 66 | 78.90258025 0.813011673453 67 | 80.3850605 0.825686835123 68 | 81.86754075 0.838018604184 69 | 83.350021 0.850000081445 70 | 85.01241875 0.863056696856 71 | 86.6748165 0.875747796628 72 | 88.33721425 0.888065056114 73 | 89.999612 0.900000150662 74 | 91.88404725 0.913112578473 75 | 93.7684825 0.925823846834 76 | 95.65291775 0.938123285882 77 | 97.537353 0.950000225755 78 | 99.70336625 0.963185968031 79 | 101.8693795 0.975924152897 80 | 104.03539275 0.98820039198 81 | 106.201406 1.00000029691 82 | 108.7381705 1.01328669729 83 | 111.274935 1.02606225208 84 | 113.8116995 1.03830683818 85 | 116.348464 1.05000033252 86 | 119.39818025 1.06342863335 87 | 122.4478965 1.07625712192 88 | 125.49761275 1.08845720079 89 | 128.547329 1.10000027255 90 | 132.3518545 1.11362907216 91 | 136.15638 1.12653208745 92 | 139.9609055 1.13866913865 93 | 143.765431 1.15000004599 94 | 148.7626155 1.16390387023 95 | 153.7598 1.17690759696 96 | 158.7569845 1.18895745226 97 | 163.754169 1.19999966227 98 | 170.78266625 1.21425500668 99 | 177.8111635 1.22738361095 100 | 184.83966075 1.23932015416 101 | 191.868158 1.24999931536 102 | 202.60949525 1.26465305769 103 | 213.3508325 1.27791621586 104 | 224.09216975 1.28972090861 105 | 234.833507 1.29999925474 106 | 252.7525725 1.31502872468 107 | 270.671638 1.32840873681 108 | 288.5907035 1.34008406116 109 | 306.509769 1.34999946779 110 | 338.69612725 1.36528423221 111 | 370.8824855 1.37872905807 112 | 403.06884375 1.3903091545 113 | 435.255202 1.39999973061 114 | 495.1693935 1.41531132904 115 | 555.083585 1.42873258195 116 | 614.9977765 1.440287455 117 | 674.911968 1.44999991385 118 | 783.26536825 1.46501788944 119 | 891.6187685 1.47830130583 120 | 999.97216875 1.48993405237 121 | 1108.325569 1.50000001839 122 | 1284.51554375 1.51441852975 123 | 1460.7055185 1.52747648709 124 | 1636.89549325 1.53929622758 125 | 1813.085468 1.55000008838 126 | 2060.11845625 1.56379869711 127 | 2307.1514445 1.5766804428 128 | 2554.18443275 1.58872200496 129 | 2801.217421 1.60000006307 130 | 3114.326345 1.61350177798 131 | 3427.435269 1.62632807053 132 | 3740.544193 1.6384903429 133 | 4053.653117 1.64999999728 134 | 4438.01656025 1.66341549459 135 | 4822.3800035 1.6762083087 136 | 5206.74344675 1.68839697222 137 | 5591.10689 1.70000001776 138 | 6055.40977075 1.71338422413 139 | 6519.7126515 1.72618791144 140 | 6984.01553225 1.738397646 141 | 7448.318413 1.74999999411 142 | 8013.44272725 1.76343593089 143 | 8578.5670415 1.77624916625 144 | 9143.69135575 1.78843781892 145 | 9708.81567 1.8000000076 146 | 10403.3446562 1.8135056489 147 | 11097.8736425 1.82635769645 148 | 11792.4026288 1.83853090176 149 | 12486.931615 1.85000001637 150 | 13375.5696222 1.86383276338 151 | 14264.2076295 1.87684067649 152 | 15152.8456368 1.888928261 153 | 16041.483644 1.90000002223 154 | 17343.467889 1.91483103513 155 | 18645.452134 1.92828510969 156 | 19947.436379 1.94009662557 157 | 21249.420624 1.94999996248 158 | 23765.4394375 1.96601872789 159 | 26281.458251 1.97976046866 160 | 28797.4770645 1.99112196008 161 | 31313.495878 1.99999997742 162 | 36882.4836277 2.01558566376 163 | 42451.4713775 2.02890239705 164 | 48020.4591273 2.04026792697 165 | 53589.446877 2.05000000321 166 | 62311.456308 2.06353368916 167 | 71033.465739 2.07612761842 168 | 79755.47517 2.08815774193 169 | 88477.484601 2.10000001067 170 | 97440.4660857 2.11229089439 171 | 106403.44757 2.12474066225 172 | 115366.429055 2.13732010216 173 | 124329.41054 2.15000000203 174 | 133239.20023 2.16266256541 175 | 142148.98992 2.17528860171 176 | 151058.77961 2.187770336 177 | 159968.5693 2.19999999332 178 | 169414.328043 2.21264032416 179 | 178860.086786 2.22508058305 180 | 188305.845529 2.23748055016 181 | 197751.604272 2.25000000567 182 | 206819.891041 2.26225564436 183 | 215888.17781 2.27472073438 184 | 224956.464578 2.28732545864 185 | 234024.751347 2.3 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ACMOP 2 | 3 | > Alternating Current Machine Optimization Project 4 | 5 | ## Requirements (Obsolete): 6 | 7 | - JMAG Designer 17.1.05i 8 | > Higher version might not work as the invoking method names might be changed. 9 | 10 | - Anaconda3-2020.02-Windows-x86_64.exe with python 3.7.6 11 | > If you use newer version of anaconda3 after 2020.02, pacakge pygmo might not work, as far as I know. 12 | 13 | - pygmo: 14 | - conda config --add channels conda-forge 15 | - conda install pygmo 16 | 17 | - pip install recordtype jsonpickle pyx pyfemm scipy soupsieve pycairo cairosvg 18 | 19 | - pyfemm, streamlit, jsonpickle, pycairo, cairosvg, pyx, and others (if any) can be installed via pip 20 | 21 | - texlive, or other latex compiler (if you want pdf report for motor design) 22 | 23 | ## Installation: 24 | 25 | - 1. Get [Anaconda3-2021.05-Windows-x86_64.exe 477.2M 2021-05-13 22:08:48](https://repo.anaconda.com/archive/) 26 | 27 | - 2. Create a virtual environment by `conda create -n your-env-name python=3.8.8 pygmo`, and then activate your virtual env by `conda activate your-env-name`. Make sure you activate your virtual env before using `pip` to install python packages. 28 | 29 | - 3. Install the rest dependencies from PyPI: `pip install pyx pyfemm jsonpickle recordtype pycairo cairosvg streamlit` 30 | > If you have network issue/error, try to use a different source, e.g.: `pip install pyx pyfemm jsonpickle recordtype pycairo cairosvg streamlit-i https://pypi.tuna.tsinghua.edu.cn/simple` 31 | 32 | - 4. If you run into the following error when executing `streamlit run visualize_everything.py`: 33 | > OSError: no library called "cairo-2" was found 34 | > no library called "cairo" was found 35 | > no library called "libcairo-2" was found 36 | > cannot load library 'libcairo.so.2': error 0x7e 37 | > cannot load library 'libcairo.2.dylib': error 0x7e 38 | > cannot load library 'libcairo-2.dll': error 0x7e 39 | 40 | Try to download the GTK2 installer `gtk2-runtime-2.24.33-2021-01-30-ts-win64.exe` or GTK3 installer from [GTK-for-Windows-Runtime-Environment-Installer](https://github.com/tschoonj/GTK-for-Windows-Runtime-Environment-Installer/releases) and see if GTK2/3 resolves the issue. (You need to restart cmd.exe to use updated PATH, the Windows environment variable.) 41 | 42 | ## Features 43 | - Restartable. Upon interrupts, this program is able to re-start from the file "swarm_data.txt" of the current run (or even from a different run). 44 | 45 | - Obtian the machine_design_variant python object after the optimization. 46 | ``` 47 | # Recover a design from its jsonpickle-object file 48 | # cd to where __p2ps1-Q12y3-0001.json is located. 49 | import sys; sys.path.insert(0, r'D:\DrH\acmop\codes3') 50 | import utility_json; variant = utility_json.from_json_recursively('p2ps1-Q12y3-0001') 51 | variant.build_jmag_project(variant.project_meta_data) 52 | ``` 53 | 54 | - Support Free Finite Element Analysis Software + Efficiency Calculation 55 | - To avoid accumlated error in the estimated magnet eddy current loss, only up to 7*resolution [Hz] is used. 56 | 57 | ## Limitations 58 | 59 | Though in most cases, a regular motor is simply a bearingless motor (that has a DPNV winding) without suspension excitation. 60 | However, there is indeed limitation. For now the only limitation I can think of is that in order to implement a DPNV winding, we must enforce the number of parallel branches to be $a=2$. This means, a motor with 3 slots are not allowed. In sitations with $a\ne 2$, results can be easily converted. With that said, one may conclude that this code should be widely applicable to regular motor as well. 61 | 62 | ## TODO (FEMM) 63 | 64 | - There is no need to draw coils smaller than slots in FEMM and this has been causing too many meshes inside the slots, slowing down FEA in FEMM. 65 | - The sliding band is drawing inside the region of mechanical air gap. Since the sleeve band is not even modeled in FEMM, maybe we should draw sliding band at the middle of the magnetic air gap. I am not sure if this will improve the meshing quality in the air gap. 66 | - Magnet eddy current is not yet considered. 67 | - Run multiple FEMM instances in parallel to sovle for transient FEA results. 68 | 69 | ## TODO (JMAG) 70 | 71 | - Currently, DPNV winding is implemented as a dual three phase system excited by current source inverter. Therefore, the JMAG circuit's (current source) excitation will give wrong terminal voltage results and the no voltage property is not observed (but the electromagnetic performance is correct because the current is correct). 72 | - Variable d_rp and d_rs can be larger than d_pm. This is not allowed for using sleeve to retain the magnet. Should add new variable like d_pm_to_iron instead. 73 | - Upon restarting, the constraints are not applied to the archive. High ripple design or low FRW may take up space in Pareto front. 74 | - The constraints are fake constraints in current optimization code implementation. For example, if the optimization is re-started from the archive, those designs with FRW<0.75 could dominate other designs even though the constraint should be FRW>0.75. 75 | - The iron loss data is only valid from 50 Hz up to 600 Hz. See http://www.femm.info/wiki/SPMLoss (Search for 530). 76 | - Make the 2.5D plot a function for group member to use (slack writing tools) 77 | - Revise build_str_results function to include rotor weight after "Average Force Mag". 78 | - Ashad: Following Pyrhonen's book to derive a BPMSM initial design (we use Bianchi 2006 for now--which is simplified) for your 160,000 rpm BPMSM. 79 | - Take care of the dummy variables in the initial design script for IM and PMSM. (At least let the user know they don't need to care about those variables.) 80 | - New re-starting feature: use the optimal design as the new template design, and use a new bound according to the new template design. 81 | - Ashad: I think the correct way of calculating error angle and magnitude for Q12p4 is by taking 5 electrical periods as suspension winding has upto 1/5 subharmonics. p^aster=5? See harmonics calculation.pdf. Jiahao: I am adding this comment from Ashad to Readme.md file of bopt-python for future development reference---the simulation setting is dependent on winding layout. 82 | - Flux weakening and strenthening have great impact on the suspension force performance/capability. This needs more research. 83 | - There is still bug in displacement power factor calculation: 84 | > Different Q6 p1 ps2 designs tested for transient decay phenomenon in 2nd TSS. 85 | > A: 3 cycles + 0.5 cycle 86 | > B: 3 cycles + 0.5 cycle + 10 cycles 87 | > [kNm/m3] [1] [%] [%] [deg] [%] [$] [1] 88 | > TRV FRW Trip Em Ea eta Cost Power factor 89 | > proj3297-SPMSM_IDQ6p1s1 | PMSM Q06p1y2 A 29.736, 0.926, 20.542, 10.372, 5.466, 93.806, 199.509, 0.947 90 | > proj3297-SPMSM_IDQ6p1s1 | PMSM Q06p1y2 B 29.790, 0.928, 20.549, 13.810, 7.654, 93.815, 199.374, 0.947 91 | > 92 | > proj3303-SPMSM_IDQ6p1s1 | PMSM Q06p1y2 A 28.749, 0.891, 23.464, 9.416, 4.963, 93.653, 200.987, 0.946 93 | > proj3303-SPMSM_IDQ6p1s1 | PMSM Q06p1y2 B 28.796, 0.892, 23.549, 12.750, 7.323, 93.662, 200.862, 0.947 94 | > 95 | > proj3376-SPMSM_IDQ6p1s1 | PMSM Q06p1y2 A 29.588, 0.943, 23.687, 12.583, 6.161, 93.767, 199.832, 0.945 96 | > proj3376-SPMSM_IDQ6p1s1 | PMSM Q06p1y2 B 29.628, 0.945, 23.724, 16.675, 9.147, 93.773, 199.733, 0.945 97 | > 98 | > proj3527-SPMSM_IDQ6p1s1 | PMSM Q06p1y2 A 29.588, 0.943, 23.687, 12.583, 6.161, 93.767, 199.832, 0.945 99 | > proj3527-SPMSM_IDQ6p1s1 | PMSM Q06p1y2 B 29.628, 0.945, 23.724, 16.675, 9.147, 93.773, 199.733, 0.945 100 | > 101 | > proj3534-SPMSM_IDQ6p1s1 | PMSM Q06p1y2 A 29.787, 0.933, 20.818, 10.372, 5.524, 93.766, 198.617, 0.947 102 | > proj3534-SPMSM_IDQ6p1s1 | PMSM Q06p1y2 B 29.842, 0.935, 20.839, 13.712, 7.580, 93.775, 198.482, 0.947 103 | > 104 | > proj3548-SPMSM_IDQ6p1s1 | PMSM Q06p1y2 A 28.703, 0.915, 17.838, 13.290, 6.590, 93.914, 193.032, 0.951 105 | > proj3548-SPMSM_IDQ6p1s1 | PMSM Q06p1y2 B 28.752, 0.917, 17.863, 17.776, 9.896, 93.922, 192.902, 0.951 106 | > 107 | > proj3747-SPMSM_IDQ6p1s1 | PMSM Q06p1y2 A 29.196, 0.950, 21.184, 10.667, 6.124, 93.467, 202.410, 0.933 108 | > proj3747-SPMSM_IDQ6p1s1 | PMSM Q06p1y2 B 29.244, 0.951, 21.236, 13.908, 6.860, 93.476, 202.290, 0.933 109 | > 110 | > proj3748-SPMSM_IDQ6p1s1 | PMSM Q06p1y2 A 29.277, 0.917, 21.198, 10.592, 5.719, 93.760, 197.828, -0.657 111 | > proj3748-SPMSM_IDQ6p1s1 | PMSM Q06p1y2 B 29.329, 0.919, 21.252, 13.779, 8.086, 93.769, 197.693, 0.949 112 | > 113 | > proj3850-SPMSM_IDQ6p1s1 | PMSM Q06p1y2 A 30.820, 1.019, 26.083, 11.041, 6.136, 93.786, 192.525, 0.945 114 | > proj3850-SPMSM_IDQ6p1s1 | PMSM Q06p1y2 B 30.867, 1.021, 26.070, 14.756, 8.258, 93.794, 192.413, 0.945 115 | > 116 | > proj3895-SPMSM_IDQ6p1s1 | PMSM Q06p1y2 A 29.329, 0.953, 21.010, 10.600, 6.140, 93.487, 202.079, 0.932 117 | > proj3895-SPMSM_IDQ6p1s1 | PMSM Q06p1y2 B 29.379, 0.955, 21.111, 13.794, 6.846, 93.496, 201.957, 0.932 118 | 119 | - More raw data should be added to json file. For example, the cost of steel, cost of PM and cost of copper. 120 | - There is a bug when building x_denorm of the initial design of induction motor, where the stator yoke depth is negative (the original stator outer radius is smaller than the computed one). 121 | 122 | - [TODO] Iron loss must use half cycle FFT as one fourth cycle could be wrong. 123 | 124 | - [TODO] Restarting optimziation with constaints applied. 125 | 126 | - [TODO] Double check winding's initial excitation position. 127 | -------------------------------------------------------------------------------- /clean_jfiles.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import shutil 4 | for el in os.listdir(): 5 | # if '_pemd' in el: 6 | # if '_Q24p1y9_restart_from_optimal_and_reevaluate_wo_csv' in el: 7 | # if '_pemd2020_prolonged - Copy' in el: 8 | 9 | if os.path.isdir(el): 10 | for _el in os.listdir(el): 11 | if os.path.isdir(el+'/'+_el): 12 | # print( _el) 13 | 14 | for __el in os.listdir(el+'/'+_el): 15 | if 'jfiles' in __el: 16 | target = el+'/'+_el + '/' +__el 17 | # if '_pemd2020/PMSM_Q24p1y9_A/proj99999.jfiles' in target: 18 | # shutil.rmtree(target) 19 | print('delete', target) 20 | 21 | # use os.walk instead 22 | def get_swarm_group(self, folder_of_collection): 23 | path = self.path2boptPython + folder_of_collection 24 | dict_path2SwarmDataOfTheSpecification = dict() 25 | dict_settingsOfTheSpecification = dict() 26 | list_specifications = [] 27 | for root, dirs, files in os.walk(path): 28 | for file in files: 29 | if 'swarm_data.txt' in file: 30 | # print(root) 31 | normpath = os.path.normpath(root) 32 | specification = normpath.split(os.sep)[-1].replace('_', ' ') # convert folder name to spec name 33 | list_specifications.append(specification) 34 | dict_path2SwarmDataOfTheSpecification[specification] = root + '/' 35 | # print(specification) 36 | if 'settings.txt' in file: 37 | with open(root+'/'+file, 'r') as f: 38 | buf = f.read() 39 | lst = buf.split('|') 40 | specification = lst[0].strip() 41 | select_fea_config_dict = lst[1].strip() 42 | dict_settingsOfTheSpecification[specification] = select_fea_config_dict 43 | return list_specifications, dict_path2SwarmDataOfTheSpecification, dict_settingsOfTheSpecification 44 | -------------------------------------------------------------------------------- /clean_large_files.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | for el in os.listdir(): 4 | # if os.path.isdir(el): 5 | if '_default' in el: 6 | for _el in os.listdir(el): 7 | if os.path.isdir(el+'/'+_el): 8 | # print( _el) 9 | 10 | for __el in os.listdir(el+'/'+_el): 11 | if '.ans' in __el: 12 | target = './' + el+'/'+_el + '/' +__el 13 | # os.remove(target) 14 | print('delete', target) 15 | 16 | elif 'jsonpickle' in __el: 17 | target_folder = el+'/'+_el + '/' +__el 18 | # shutil.rmtree(target_folder) 19 | print('delete', target_folder) 20 | 21 | # use os.walk instead 22 | def get_swarm_group(self, folder_of_collection): 23 | path = self.path2boptPython + folder_of_collection 24 | dict_path2SwarmDataOfTheSpecification = dict() 25 | dict_settingsOfTheSpecification = dict() 26 | list_specifications = [] 27 | for root, dirs, files in os.walk(path): 28 | for file in files: 29 | if 'swarm_data.txt' in file: 30 | # print(root) 31 | normpath = os.path.normpath(root) 32 | specification = normpath.split(os.sep)[-1].replace('_', ' ') # convert folder name to spec name 33 | list_specifications.append(specification) 34 | dict_path2SwarmDataOfTheSpecification[specification] = root + '/' 35 | # print(specification) 36 | if 'settings.txt' in file: 37 | with open(root+'/'+file, 'r') as f: 38 | buf = f.read() 39 | lst = buf.split('|') 40 | specification = lst[0].strip() 41 | select_fea_config_dict = lst[1].strip() 42 | dict_settingsOfTheSpecification[specification] = select_fea_config_dict 43 | return list_specifications, dict_path2SwarmDataOfTheSpecification, dict_settingsOfTheSpecification 44 | -------------------------------------------------------------------------------- /codes3/CrossSectInnerSalientPoleRotor.py: -------------------------------------------------------------------------------- 1 | from pylab import np, cos, sin 2 | EPS = 1e-3 # [mm] 3 | 4 | class ExceptionBadDesign(Exception): 5 | """Exception for notifying bad design.""" 6 | def __init__(self, message, payload=None): 7 | self.message = message 8 | self.payload = 'you could add more args here' 9 | def __str__(self): 10 | return str(self.message) 11 | 12 | class CrossSectInnerSalientPoleRotorV1(object): 13 | def __init__(self, 14 | name = 'SalientPoleRotor', 15 | color = '#FE840E', 16 | mm_r_ro = 40, 17 | mm_d_sleeve = 1.5, 18 | split_ratio_rotor_salient = 0.2, 19 | deg_alpha_rsp = 10, 20 | pm = 2, 21 | mm_r_ri = 5, 22 | location = None 23 | ): 24 | self.name = name 25 | self.color = color 26 | self.mm_r_ro = mm_r_ro 27 | self.mm_d_sleeve = mm_d_sleeve 28 | self.split_ratio_rotor_salient = split_ratio_rotor_salient 29 | self.deg_alpha_rsp = deg_alpha_rsp 30 | self.pm = pm # number of rotor modulator pole pairs 31 | self.mm_r_ri = mm_r_ri 32 | self.location = location # move this part to another location other than origin (not supported yet) 33 | 34 | def draw(self, drawer, bool_draw_whole_model=False): 35 | 36 | drawer.getSketch(self.name, self.color) 37 | 38 | mm_r_ro = self.mm_r_ro 39 | mm_d_sleeve = self.mm_d_sleeve 40 | split_ratio_rotor_salient = self.split_ratio_rotor_salient 41 | mm_d_rsp = split_ratio_rotor_salient * mm_r_ro 42 | # print('DEBUG: mm_d_rsp=', mm_d_rsp) 43 | deg_alpha_rsp = self.deg_alpha_rsp 44 | pm = self.pm 45 | alpha_rp = 2*np.pi/(2*pm) # pole span 46 | 47 | P1 = [mm_r_ro * cos(0.5*deg_alpha_rsp/180*np.pi), - mm_r_ro * sin(0.5*deg_alpha_rsp/180*np.pi)] 48 | P2 = [mm_r_ro * cos(0.5*deg_alpha_rsp/180*np.pi), + mm_r_ro * sin(0.5*deg_alpha_rsp/180*np.pi)] 49 | P3 = [P2[0] - mm_d_rsp, P2[1]] 50 | P0 = [P1[0] - mm_d_rsp, P1[1]] 51 | def iPark(P, theta): 52 | return [P[0]*np.cos(theta)+P[1]*-np.sin(theta), P[0]*np.sin(theta)+P[1]*np.cos(theta)] 53 | P4 = iPark(P0, alpha_rp) 54 | 55 | list_segments = [] 56 | if bool_draw_whole_model: 57 | def draw_fraction(list_segments, P1, P2, P3, P4): 58 | # P1 = iPark(P1, 45/2/180*np.pi) # rotate the rotor by an angle of 22.5 deg 59 | # P2 = iPark(P2, 45/2/180*np.pi) # rotate the rotor by an angle of 22.5 deg 60 | # P3 = iPark(P3, 45/2/180*np.pi) # rotate the rotor by an angle of 22.5 deg 61 | # P4 = iPark(P4, 45/2/180*np.pi) # rotate the rotor by an angle of 22.5 deg 62 | list_segments += drawer.drawArc([0,0], P1, P2) 63 | list_segments += drawer.drawLine(P2, P3) 64 | list_segments += drawer.drawArc([0,0], P3, P4) 65 | P1_CCW = iPark(P1, alpha_rp) 66 | list_segments += drawer.drawLine(P1_CCW, P4) 67 | for i in range(2*pm): 68 | draw_fraction(list_segments, iPark(P1, i*alpha_rp), 69 | iPark(P2, i*alpha_rp), 70 | iPark(P3, i*alpha_rp), 71 | iPark(P4, i*alpha_rp)) 72 | # draw a circle (this is officially suggested by FEMM) 73 | PRI = [self.mm_r_ri, 0 ] 74 | list_segments += drawer.drawArc([0,0], PRI, [-PRI[0], PRI[1]]) 75 | list_segments += drawer.drawArc([0,0], [-PRI[0], PRI[1]], PRI) 76 | 77 | innerCoord = ( 0.5*(P1[0]+P4[0]), 0.5*(P1[1]+P4[1])) 78 | 79 | # return [list_segments] # csToken # cross section token 80 | return {'innerCoord': innerCoord, 81 | 'list_regions':[list_segments], 82 | 'mirrorAxis': None, 83 | 'inner_or_outer_region_to_remove': [True, False] 84 | } 85 | else: 86 | raise Exception('Not supported. Please set bool_draw_whole_model to True.') 87 | 88 | class CrossSectInnerSalientPoleRotorV2(object): 89 | def __init__(self, 90 | name = 'SalientPoleRotor', 91 | color = '#FE840E', 92 | mm_r_ro = 40, 93 | mm_d_sleeve = 1.5, 94 | split_ratio_rotor_salient = 0.2, 95 | deg_alpha_rsp = 10, 96 | pm = 2, 97 | mm_r_ri = 5, 98 | location = None 99 | ): 100 | self.name = name 101 | self.color = color 102 | self.mm_r_ro = mm_r_ro 103 | self.mm_d_sleeve = mm_d_sleeve 104 | self.split_ratio_rotor_salient = split_ratio_rotor_salient 105 | self.deg_alpha_rsp = deg_alpha_rsp 106 | self.pm = pm # number of rotor modulator pole pairs 107 | self.mm_r_ri = mm_r_ri 108 | self.location = location # move this part to another location other than origin (not supported yet) 109 | 110 | def draw(self, drawer, bool_draw_whole_model=False): 111 | 112 | drawer.getSketch(self.name, self.color) 113 | 114 | mm_r_ro = self.mm_r_ro 115 | mm_d_sleeve = self.mm_d_sleeve 116 | split_ratio_rotor_salient = self.split_ratio_rotor_salient 117 | mm_d_rsp = split_ratio_rotor_salient * mm_r_ro 118 | # print('DEBUG: mm_d_rsp=', mm_d_rsp) 119 | deg_alpha_rsp = self.deg_alpha_rsp 120 | pm = self.pm 121 | 122 | mm_r_ri = self.mm_r_ri 123 | 124 | alpha_rp = 2*np.pi/pm # pole span 125 | deg_span_angle_rotor_pole_pair = 360/pm 126 | 127 | P0 = [mm_r_ro, 0] 128 | P1 = [mm_r_ro*cos(deg_alpha_rsp/180*np.pi/2), 129 | mm_r_ro*-sin(deg_alpha_rsp/180*np.pi/2)] 130 | mm_r_groove = mm_r_ro * (1-split_ratio_rotor_salient) 131 | 132 | def iPark(P, theta): 133 | return [P[0]*np.cos(theta)+P[1]*-np.sin(theta), P[0]*np.sin(theta)+P[1]*np.cos(theta)] 134 | 135 | # print(P1, deg_alpha_rsp) 136 | if False: # Option provided by Shi Zhou with some approximation 137 | P3 = [ mm_r_groove * cos(deg_span_angle_rotor_pole_pair/180*np.pi), 138 | mm_r_groove * -sin(deg_span_angle_rotor_pole_pair/180*np.pi) ] 139 | rad_temp = (mm_r_ro - mm_r_groove) * np.tan(8/180*np.pi) / mm_r_groove 140 | rad_P3_rotate_angle = deg_span_angle_rotor_pole_pair/180*np.pi - (deg_alpha_rsp / 2 / 180*np.pi + rad_temp) 141 | P2 = iPark(P3, rad_P3_rotate_angle) 142 | else: 143 | slope = np.tan(8/180*np.pi) 144 | the_other_point_on_the_line = [P1[0]-1, P1[1] + slope * (-1)] 145 | intersections = self.get_node_at_intersection(c=[[0,0], mm_r_groove], l=[[P1[0], P1[1]], the_other_point_on_the_line]) 146 | for point in intersections: 147 | # print(point) 148 | if point[0]>0: 149 | P2 = positive_point = point 150 | # print(P2) 151 | # raise 152 | # P4 = iPark(P0, alpha_rp) 153 | 154 | P1_Mirror_CCW = P1_Mirror = P1[0], -P1[1] 155 | # print(P1_Mirror, P1) 156 | P2_Mirror_CCW = P2_Mirror = P2[0], -P2[1] 157 | P2_Rotate = iPark(P2, alpha_rp) 158 | 159 | angle_at_P2 = np.arctan2(P2[1], P2[0]) 160 | 161 | P2_InnerEdge = [self.mm_r_ri* cos(angle_at_P2), 162 | self.mm_r_ri* sin(angle_at_P2) ] 163 | P2_InnerEdge_Rotate = iPark(P2_InnerEdge, alpha_rp) 164 | 165 | list_segments = [] 166 | if bool_draw_whole_model: 167 | def draw_fraction(list_segments, P1, P2, P2_Rotate, P1_Mirror_CCW, P2_Mirror_CCW): 168 | # P1 = iPark(P1, 45/2/180*np.pi) # rotate the rotor by an angle of 22.5 deg 169 | # P2 = iPark(P2, 45/2/180*np.pi) # rotate the rotor by an angle of 22.5 deg 170 | # P2_Rotate = iPark(P2_Rotate, 45/2/180*np.pi) # rotate the rotor by an angle of 22.5 deg 171 | # P4 = iPark(P4, 45/2/180*np.pi) # rotate the rotor by an angle of 22.5 deg 172 | list_segments += drawer.drawArc([0,0], P2_Mirror_CCW, P2_Rotate) 173 | list_segments += drawer.drawLine(P2_Mirror_CCW, P1_Mirror_CCW) 174 | list_segments += drawer.drawArc([0,0], P1, P1_Mirror_CCW) 175 | list_segments += drawer.drawLine(P2, P1) 176 | # list_segments += drawer.drawLine(P1_CCW, P4) 177 | for i in range(pm): 178 | draw_fraction(list_segments, iPark(P1, i*alpha_rp), 179 | iPark(P2, i*alpha_rp), 180 | iPark(P2_Rotate, i*alpha_rp), 181 | iPark(P1_Mirror_CCW, i*alpha_rp), 182 | iPark(P2_Mirror_CCW, i*alpha_rp), 183 | ) 184 | # if i==1: 185 | # break 186 | # raise 187 | # draw a circle (this is officially suggested by FEMM) 188 | PRI = [self.mm_r_ri, 0 ] 189 | list_segments += drawer.drawArc([0,0], PRI, [-PRI[0], PRI[1]]) 190 | list_segments += drawer.drawArc([0,0], [-PRI[0], PRI[1]], PRI) 191 | 192 | # innerCoord = ( 0.5*(P1[0]+P4[0]), 0.5*(P1[1]+P4[1])) 193 | 194 | # return [list_segments] # csToken # cross section token 195 | return {#'innerCoord': innerCoord, 196 | 'list_regions':[list_segments], 197 | 'mirrorAxis': None, 198 | 'inner_or_outer_region_to_remove': [True, False] 199 | } 200 | else: 201 | list_segments += drawer.drawArc([0,0], P2_Mirror, P2_Rotate) 202 | list_segments += drawer.drawLine(P2_Mirror, P1_Mirror) 203 | list_segments += drawer.drawArc([0,0], P1, P1_Mirror) 204 | # raise 205 | list_segments += drawer.drawLine(P2, P1) 206 | list_segments += drawer.drawLine(P2, P2_InnerEdge) 207 | list_segments += drawer.drawArc([0,0], P2_InnerEdge, P2_InnerEdge_Rotate) 208 | list_segments += drawer.drawLine(P2_InnerEdge_Rotate, P2_Rotate) 209 | # draw a circle (this is officially suggested by FEMM) 210 | PRI = [self.mm_r_ri, 0 ] 211 | # list_segments += drawer.drawArc([0,0], PRI, [-PRI[0], PRI[1]]) 212 | # list_segments += drawer.drawArc([0,0], [-PRI[0], PRI[1]], PRI) 213 | 214 | # innerCoord = ( 0.5*(P1[0]+P4[0]), 0.5*(P1[1]+P4[1])) 215 | 216 | # return [list_segments] # csToken # cross section token 217 | return {#'innerCoord': innerCoord, 218 | 'list_regions':[list_segments], 219 | 'mirrorAxis': None, 220 | # 'inner_or_outer_region_to_remove': [True, False] 221 | } 222 | raise Exception('Not supported. Please set bool_draw_whole_model to True.') 223 | 224 | 225 | @staticmethod 226 | def get_node_at_intersection(c,l): 227 | # this works for c and l having one or two intersections 228 | if c[0][0] != 0 or c[0][1] != 0: 229 | raise Exception('Not implemented for non-origin centered circle.') 230 | r = c[1] 231 | c = None 232 | x1, y1 = l[0][0], l[0][1] 233 | x2, y2 = l[1][0], l[1][1] 234 | if x1 == x2: 235 | raise Exception('Not implemented.') 236 | a = -(y2-y1)/(x2-x1) 237 | b = 1 238 | c = y1-(y2-y1)/(x2-x1)*x1 239 | Delta = np.sqrt(r**2*(a**2+b**2)-c**2) 240 | if Delta < 0: 241 | raise Exception('No intersection for given line and circle') 242 | x_solutions = (a*c + b*Delta)/(a**2+b**2), (a*c - b*Delta)/(a**2+b**2) 243 | y_solutions = (b*c - a*Delta)/(a**2+b**2), (b*c + a*Delta)/(a**2+b**2) 244 | return (x_solutions[0], y_solutions[0]), (x_solutions[1], y_solutions[1]) 245 | -------------------------------------------------------------------------------- /codes3/CrossSectToken.py: -------------------------------------------------------------------------------- 1 | class CrossSectToken(object): 2 | # CROSSSECTTOKEN Data generated upon drawing a cross-section 3 | # Objects of this class contain data that was generated when a 4 | # cross-section is drawn. 5 | token = [] # Untyped token created by the drawing tool 6 | innerCoord # x,y coordinate that is located somewhere inside the cross-section (does not have to be the center 7 | 8 | def __init__(self, innerCoord, token): 9 | # CROSSSECTTOKEN Constructor of a CrossSectToken with a token 10 | # This cross-section token object consists of a coordinate 11 | # and a token 12 | self.innerCoord = innerCoord # TO DO: How to deal with units?? 13 | self.token = token 14 | 15 | -------------------------------------------------------------------------------- /codes3/DesignParametersReproduced: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horychen/ACMOP/1ec13664f6696af2c2470679fe3a17340735df33/codes3/DesignParametersReproduced -------------------------------------------------------------------------------- /codes3/Location2D.py: -------------------------------------------------------------------------------- 1 | from pylab import np 2 | class Location2D(object): 3 | # LOCATION2D Indicates a cross section's location 4 | def __init__(self, anchor_xy, deg_theta): 5 | 6 | self.anchor_xy = anchor_xy #Distance from global origin xy coordinate to component's origin xy coordinate 7 | 8 | self.theta = deg_theta * np.pi/180 # Angles about global xy axes to 9 | # rotate component's xy axes in radians 10 | 11 | def transformCoords(self, points, deg_addTheta=0): 12 | # This function takes in an nx2 array of coordinates of the form 13 | # [x,y] and returns rotated and translated coordinates. The 14 | # translation and rotation are described by obj.anchor_xy and 15 | # obj.theta. The optional "addTheta" argument adds an 16 | # additional angle of "addTheta" to the obj.theta attribute. 17 | 18 | transCoords = [] 19 | for point in points: 20 | 21 | # 旋转方向和eMach是反一下的,我是Park变换。 22 | cosT = np.cos( self.theta + (deg_addTheta*np.pi/180) ) 23 | sinT = np.sin( self.theta + (deg_addTheta*np.pi/180) ) 24 | 25 | transCoords.append( [ points[0]*cosT + points[1]*sinT, 26 | points[0]*-sinT + points[1]*cosT ] ) 27 | return np.array(transCoords) 28 | -------------------------------------------------------------------------------- /codes3/PSWinding_CommonRing_DesignTable_Generator.py: -------------------------------------------------------------------------------- 1 | class ValidSet_of_PoleSpecificWindingWithNeutralPlate(object): 2 | def __init__(self, layers): 3 | print('_________________________\nlayers =', layers) # =1 or =2 4 | 5 | # ps in N_Even for single layer winding and k in N for double layer winding 6 | for ps in range(2, MAX_PS, 3-layers): 7 | print('\t' + 'ps =', ps) 8 | 9 | # p = ps +- 1 10 | for p in [ps-1, ps+1]: 11 | print('\t'*2 + f'p = {p:d}', end=' ') 12 | valid_Qr = self.get_ValidSet_of_Qr(QS, p) 13 | print('| Valid Qr set:', valid_Qr) 14 | 15 | # k1 in N < ps 16 | for k1 in range(1, ps): 17 | print('\t'*3 + 'k1 =', k1) 18 | 19 | # c/d = reduced( k1/ps ) 20 | c = Fraction(k1, ps).numerator 21 | d = Fraction(k1, ps).denominator 22 | print('\t'*3 + 'c/d = %d/%d'%(c, d)) 23 | 24 | # k in N_Even for single layer winding and k in N for double layer winding 25 | # list_of_k = [k for k in range(1, MAX_K) if k%2==0] if layers==1 else range(1, MAX_K) # this turns out to be wrong 26 | list_of_k = [k for k in range(1, MAX_K)] 27 | for k in list_of_k: 28 | print('\t'*4 + 'k=%3d'%(k), end='') 29 | 30 | Qr = k*d 31 | print(' ' + 'Qr=%3d'%(Qr), end='') 32 | if Qr not in valid_Qr: 33 | print() 34 | continue 35 | 36 | nl = Qr/2 + 1 if layers == 1 else 0 37 | print(' ' + 'nl=%2d'%(nl), end='') 38 | 39 | y = k*c 40 | print(' ' + 'y=%3d'%(y), end='') 41 | 42 | # This step is core. Pick an m to enforce z=1 (q=z/n). 43 | m = Fraction(Qr, 2*p).numerator 44 | n = Fraction(Qr, 2*p).denominator 45 | print(' ' + 'm/n=%3d/%d'%(m,n), end='') 46 | 47 | # fractional slot winding? 48 | print(' ISW ' if n ==1 else ' _FSW', end='') 49 | 50 | # SPP 51 | print(' ' + 'q=z/n=%d/%d'%(Fraction(Qr,2*p*m).numerator, Fraction(Qr,2*p*m).denominator), end='') 52 | 53 | # coil pitch 54 | p_aster_gamma = p*y/Qr*360 55 | while p_aster_gamma > 360: p_aster_gamma -= 360 56 | if p_aster_gamma > 180: p_aster_gamma = 360 - p_aster_gamma 57 | print(' ' + 'p*gamma=%3g'%(p_aster_gamma), end='') 58 | 59 | # winding factor = pitch factor because z=1, i.e., no distribution, i.e., one coil per coil group 60 | gamma = y/Qr * 2*math.pi 61 | print(' ' + 'kw_h = sin(h*gamma/2) =', end='') 62 | for h in range(1, 10): 63 | kw_h = math.sin(h*gamma/2) 64 | print(f'{kw_h:.2f}', end=', ') 65 | print('...') 66 | 67 | def get_ValidSet_of_Qr(self, Qs, p): 68 | valid_Qr = [] 69 | for Qr in range(2, 2*Qs, 2): 70 | if abs(Qs - Qr) == 2 \ 71 | or abs(Qs - Qr) == 2*p+1 \ 72 | or abs(Qs - Qr) == 2*p-1 \ 73 | or Qr == Qs \ 74 | or Qr == 0.5*Qs \ 75 | or Qr == 2*Qs\ 76 | or abs(Qs - Qr) == 2*p+2 \ 77 | or abs(Qs - Qr) == 2*p-2: 78 | # or Qr > 1.25*Qs \ 79 | # or Qr % (6*p) == 0 \ 80 | # or (Qr+2*p) % (6*p) == 0 \ 81 | # or (Qr-2*p) % (6*p) == 0 \ 82 | # or abs(Qr-Qs) == 2*p \ 83 | # or abs(Qr-2*Qs) == 2*p \ 84 | # or abs(Qr-Qs) == p \ 85 | # or abs(Qr-0.5*Qs) == p: 86 | continue 87 | else: 88 | valid_Qr.append(Qr) 89 | return valid_Qr 90 | 91 | def get_design(self, layers, ps, p, k1, k): 92 | # c/d = reduced( k1/ps ) 93 | self.c = c = Fraction(k1, ps).numerator 94 | self.d = d = Fraction(k1, ps).denominator 95 | print('c/d\t= %d/%d'%(c, d)) 96 | 97 | self.Qr = Qr = k*d 98 | print(f'Qr\t= {Qr}') 99 | print(f'p\t= {p}') 100 | print(f'ps\t= {ps}') 101 | print(f'k1\t= {k1}') 102 | print(f'k\t= {k}') 103 | print(f't\t= {math.gcd(Qr,p)}') 104 | 105 | self.nl = Qr/2 + 1 if layers == 1 else 0 106 | print('nl\t= %2d'%(self.nl)) 107 | 108 | y = k*c 109 | self.coil_pitch_y = coil_pitch_y = y 110 | print('y\t= %3d'%(y)) 111 | 112 | # This step is core. Pick an m to enforce z=1 (q=z/n). 113 | self.m = m = Fraction(Qr, 2*p).numerator 114 | self.n = n = Fraction(Qr, 2*p).denominator 115 | print('m/n\t= %3d/%d'%(m,n), '(this is consistent with Pyrhonen@(2.83): m cannot be multiples of n.)') 116 | 117 | # fractional slot winding? 118 | print('ISW' if n==1 else 'FSW') 119 | 120 | # SPP 121 | self.q = Qr/(2*p*m) 122 | self.z = z = Fraction(Qr,2*p*m).numerator 123 | self.n = n = Fraction(Qr,2*p*m).denominator 124 | print(f'q=z/n={z:d}/{n:d} = {Qr:d}/(2*{p}*{m})') 125 | 126 | # coil pitch 127 | p_aster_gamma = p*y/Qr*360 128 | while p_aster_gamma > 360: p_aster_gamma -= 360 129 | if p_aster_gamma > 180: p_aster_gamma = 360 - p_aster_gamma 130 | print('p*gamma\t=%3g'%(p_aster_gamma)) 131 | 132 | # winding factor = pitch factor because z=1, i.e., no distribution, i.e., one coil per coil group 133 | self.gamma = gamma = y/Qr * 2*math.pi 134 | print('kw_h = sin(h*gamma/2) =', end='') 135 | for h in range(1, 10): 136 | kw_h = math.sin(h*gamma/2) 137 | print(f'{kw_h:.2f}', end=', ') 138 | print('...') 139 | 140 | 141 | if layers == 1: 142 | number_of_coils = Qr/2 # each coil takes up two slots. 143 | elif layers == 2: 144 | number_of_coils = Qr 145 | self.number_of_coils = number_of_coils 146 | print('number_of_coils =', number_of_coils) 147 | print('number of coils per phase =', number_of_coils / m) 148 | 149 | 150 | # --------------------------------------------------- Tentative winding layout 151 | char_bias = 97 152 | sU = '' 153 | sL = '' 154 | self.pairs = [] 155 | # y*Qr' (number of slot in a sub-cage) 156 | if y*2 == Qr and layers==1: 157 | for i in range(y): 158 | sU += ' | ' + chr(char_bias+i) 159 | self.pairs.append( (i+1, i+y+1) ) 160 | for ind, _ in enumerate(range(y, Qr)): 161 | sU += ' | ' + chr(char_bias+ind) 162 | elif y*3 == Qr and layers==2: 163 | for i in range(Qr-y): 164 | sU += ' | ' + chr(char_bias+i) 165 | # sL += ' | ' + chr(char_bias+Qr+i-y) 166 | self.pairs.append( (i+1, i+y+1) ) 167 | for i in range(Qr-y, Qr): 168 | sU += ' | ' + chr(char_bias+i) 169 | # sL += ' | ' + chr(char_bias+i+y) 170 | self.pairs.append( (i+1, i+y-Qr+1) ) 171 | elif y*4 == Qr and layers==2: 172 | # TODO: 直接照抄y*3==Qr的代码,可以吗? 173 | for i in range(Qr-y): 174 | sU += ' | ' + chr(char_bias+i) 175 | # sL += ' | ' + chr(char_bias+Qr+i-y) 176 | self.pairs.append( (i+1, i+y+1) ) 177 | for i in range(Qr-y, Qr): 178 | sU += ' | ' + chr(char_bias+i) 179 | # sL += ' | ' + chr(char_bias+i+y) 180 | self.pairs.append( (i+1, i+y-Qr+1) ) 181 | else: 182 | raise Exception('Not implemented.') 183 | print(f'----\nWinding Layout (m={m:d} phases):') 184 | print("".join([f' |{i+1:2d}' for i in range(Qr)])) 185 | print(sU) 186 | print(sL) 187 | print('\n'*3) 188 | print('----------Winding layout transcript used in winding_layout.py is as follows:') 189 | print(r''' 190 | if Qr == %d \ 191 | and p == %d \ 192 | and ps == %d \ 193 | and coil_pitch_y == %d:'''%(Qr,p,ps,coil_pitch_y), end='\n\t\t\tself.pairs = ') 194 | if layers == 2: 195 | print(design.reduce_to_single_layer()) 196 | else: 197 | print(self.pairs) 198 | 199 | def reduce_to_single_layer(self): 200 | list_groups = [] 201 | # print('\n|||', self.pairs) 202 | # quit() 203 | for _, target in enumerate(self.pairs): 204 | bool_continue = False 205 | for group in list_groups: 206 | # print(target[0], 'in', group, '?') # 11 in (1, 11, 21) ? 207 | if target[0] in group: 208 | bool_continue = True # avoid to include (1, 11, 21) and (11, 21, 1) at the same time 209 | if bool_continue == True: 210 | continue 211 | 212 | # print(target, end=' | ') 213 | bool_appended = False 214 | group = list(target) # [1, 11] | [11, 21] 215 | for i in range(_+1, len(self.pairs)): 216 | pair = self.pairs[i] # (2, 12), (11, 21) | (1, 11) 217 | if pair[0] in group: # 2, 11 | 1 218 | if pair[1] not in group: 219 | group.append(pair[1]) 220 | bool_appended = True 221 | if pair[1] in group: # 12, 21 | 11 222 | if pair[0] not in group: 223 | group.append(pair[0]) 224 | bool_appended = True 225 | # print(group) 226 | if bool_appended: 227 | list_groups.append(tuple(group)) 228 | 229 | # print('Before:', self.pairs) 230 | # print('After:', list_groups) 231 | return list_groups 232 | 233 | if __name__ == '__main__': 234 | # layers = 1; QS = 36 235 | # layers = 1; QS = 24 236 | 237 | # layers = 2; QS = 24 238 | # layers = 2; QS = 18 239 | layers = 2; QS = 36 240 | 241 | print(f'For Qs={QS:d}, find valid set of pole specific winding with neutral plate.') 242 | from fractions import Fraction 243 | import math 244 | MAX_K = 20+1 245 | MAX_PS = 4+1 246 | 247 | 248 | 249 | 250 | import os, sys 251 | # sys.stdout = open(os.devnull, "w") 252 | design = ValidSet_of_PoleSpecificWindingWithNeutralPlate(layers=layers) 253 | sys.stdout = sys.__stdout__ 254 | 255 | # print('\n'+'~*'*40) 256 | # design.get_design(layers=1, ps=2, p=3, k1=1, k=12) 257 | # print('\n'+'~*'*40) 258 | # design.get_design(layers=1, ps=2, p=3, k1=1, k=10) 259 | 260 | # print('\n'+'~*'*40) 261 | # design.get_design(layers=2, ps=3, p=2, k1=1, k=10) 262 | 263 | print('\n'+'~*'*40) 264 | # design.get_design(layers=2, ps=3, p=2, k1=1, k=6) 265 | # design.get_design(layers=2, ps=4, p=3, k1=1, k=5) # Mar. 02 2021, TEC-ISMB-2021 266 | design.get_design(layers=2, ps=4, p=3, k1=1, k=4) # April 24 2021 267 | # design.get_design(layers=2, ps=4, p=3, k1=1, k=3) # April 24 2021 268 | 269 | 270 | -------------------------------------------------------------------------------- /codes3/ParetoFrontPlotOverlapped.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.insert(0, './codes3/') 3 | import pyrhonen_procedure_as_function # main_utility, 4 | import utility_postprocess 5 | 6 | from pylab import mpl, np, plt 7 | mpl.rcParams['mathtext.fontset'] = 'stix' 8 | mpl.rcParams['font.family'] = 'STIXGeneral' 9 | 10 | # mpl.style.use('classic') 11 | mpl.rcParams['legend.fontsize'] = 12.5 12 | # mpl.rcParams['legend.family'] = 'Times New Roman' 13 | mpl.rcParams['font.family'] = ['Times New Roman'] 14 | mpl.rcParams['font.size'] = 14.0 15 | font = {'family' : 'Times New Roman', #'serif', 16 | 'color' : 'darkblue', 17 | 'weight' : 'normal', 18 | 'size' : 14,} 19 | textfont = {'family' : 'Times New Roman', #'serif', 20 | 'color' : 'darkblue', 21 | 'weight' : 'normal', 22 | 'size' : 11.5,} 23 | plt.rc('text', usetex=True) # https://github.com/matplotlib/matplotlib/issues/4495/ 24 | plt.rc('pgf', texsystem='pdflatex') 25 | fig, ax = plt.subplots(figsize=(8,5), constrained_layout=False) 26 | fig.set_rasterized(True) # https://stackoverflow.com/questions/19638773/matplotlib-plots-lose-transparency-when-saving-as-ps-eps 27 | plt.subplots_adjust(left=None, bottom=None, right=0.85, top=None, wspace=None, hspace=None) 28 | 29 | def load_settings(select_spec, select_fea_config_dict, path2swarmData, bool_post_processing=False): 30 | import json, os 31 | # print(__file__[:-len('main_utility.py')]+'./machine_specifications.json', 'r') 32 | with open(os.path.dirname(__file__)+'/machine_specifications.json', 'r') as f: 33 | raw_specs = json.load(f) 34 | with open(os.path.dirname(__file__)+'/machine_simulation.json', 'r') as f: 35 | raw_fea_config_dicts = json.load(f) 36 | # quit() 37 | 38 | def decode_raw_specs(raw_specs, select_spec=None): 39 | for key, val in raw_specs.items(): 40 | print('\n', key) 41 | for ke, va in val.items(): 42 | print('\t', ke) 43 | for k, v in va.items(): 44 | print('\t\t', k + ':', v) 45 | # decode_raw_specs(raw_specs, select_spec) 46 | def decode_raw_fea_configs(raw_fea_config_dicts): 47 | for key, val in raw_fea_config_dicts.items(): 48 | print('\n', key) 49 | for ke, va in val.items(): 50 | print('\t', ke+':', va) 51 | # decode_raw_fea_configs(raw_fea_config_dicts) 52 | 53 | spec_input_dict = raw_specs[select_spec]['Inputs'] 54 | fea_config_dict = raw_fea_config_dicts[select_fea_config_dict] 55 | fea_config_dict['bool_post_processing'] = bool_post_processing 56 | 57 | import where_am_i 58 | where_am_i.where_am_i_v2(fea_config_dict, bool_post_processing) 59 | fea_config_dict['run_folder'] = path2swarmData 60 | fea_config_dict['output_dir'] = path2swarmData 61 | 62 | # output_dir = fea_config_dict['dir.parent'] + fea_config_dict['run_folder'][:-1] + r'_json_files/' 63 | output_dir = fea_config_dict['run_folder'][:-1] + r'_json_files/' 64 | 65 | # create output folder only when not post-processing? No, sometimes in post-processing we run FEA simulation. 66 | if not os.path.exists(output_dir): 67 | os.makedirs(output_dir) 68 | print('\t[main_utility.py]', output_dir) 69 | with open(output_dir+'settings.txt', 'w') as f: 70 | f.write(select_spec + ' | ' + select_fea_config_dict) 71 | # print(spec_input_dict) 72 | # quit() 73 | 74 | return output_dir, spec_input_dict, fea_config_dict 75 | 76 | if True: 77 | # PEMD 2020 78 | specs = [ "PMSM Q06p1y2 A", 79 | "PMSM Q06p2y1 A", 80 | "PMSM Q12p1y5 A", 81 | "PMSM Q12p2y3 A", 82 | "PMSM Q12p4y1 A", 83 | "PMSM Q12p4y1 A", 84 | "PMSM Q24p1y9 A" ] 85 | labels = [ 'Q6p1', 'Q6p2', 'Q12p1', 'Q12p2', 'Q12p4', 'Q12p4', 'Q24p1' ] 86 | if True: 87 | # 88 | folder_of_collection = 'C:\lab\_chenjh2\ACMOP\_PEMD_2020_swarm_data_collected/' 89 | data_folder_names = ['_Q06p1y2_restart_from_optimal_and_reevaluate_wo_csv/', 90 | '_Q06p2y1_restart_from_optimal_and_reevaluate_wo_csv/', 91 | '_Q12p1y5_restart_from_optimal_and_reevaluate_wo_csv/', 92 | '_Q12p2y3_restart_from_optimal_and_reevaluate_wo_csv/', 93 | '_Q12p4y1_restart_from_optimal_and_reevaluate_wo_csv/', 94 | '_Q12p4y1_restart_from_optimal_and_reevaluate_wo_csv_Subharmonics/', 95 | '_Q24p1y9_restart_from_optimal_and_reevaluate_wo_csv/'] 96 | markers = [ '$1$', '$2$', '$3$', '$4$', '$5$', '$8$', '$6$' ] # [ ',', '+', '.', 'o', '*' ] 97 | plotting_setting = 2 98 | select_fea_config_dict = '#02 JMAG PMSM Evaluation Setting' 99 | else: 100 | # re-optimize since Eric found a bug in steel cost calculation 101 | folder_of_collection = '_re0_swarm_data_collected/' 102 | data_folder_names = ['_re0_Q06p1y2/', 103 | '_re0_Q06p2y1/', 104 | '_re0_Q12p1y5/', 105 | '_re0_Q12p2y3/', 106 | '_re0_Q12p4y1/', 107 | '_re0_Q12p4y1_Subharmonics/', 108 | '_re0_Q24p1y9/' ] 109 | markers = [ '$1$', '$2$', '$3$', '$4$', None, '$5$', '$6$' ] 110 | plotting_setting = 3 111 | select_fea_config_dict = '#02 JMAG PMSM Evaluation Setting' 112 | elif False: 113 | # TIA-IEMDC-ECCE 2019 114 | specs = [ "IM Q24p1y9 A", "IM Q24p1y9 Qr32", "IM Q24p2y6 Qr16", "IM Q24p2y6 Qr32"] 115 | labels = [ '$p=1,Q_r=16$', '$p=1,Q_r=32$', '$p=2,Q_r=16$', '$p=2,Q_r=32$'] 116 | # optimize 2 pole induction motor for tia-iemdc-ecce paper 117 | folder_of_collection = '_TIA_IEMDC_swarm_data_collected/' 118 | data_folder_names = ['Q24p1y9/', 119 | 'Q24p1y9/', 120 | 'Q24p2y6/', 121 | 'Q24p2y6/'] 122 | markers = [ '$1$', '$2$', '$3$', '$4$' ] 123 | plotting_setting = 4 124 | select_fea_config_dict = '#011 JMAG IM Re-evaluation wo/ CSV Setting' 125 | else: 126 | # TIA-ISMB 2020 127 | folder_of_collection = '_TIA_ISMB_swarm_data_collected/' 128 | specs = [ "IM Q24p1y9 Qr14 Round Bar", 129 | "IM Q24p1y9 Qr16 Round Bar", 130 | "IM Q36p3y5ps2 Qr20-FSW Round Bar Separate Winding", 131 | "IM Q36p3y5ps2 Qr24-ISW Round Bar Separate Winding", 132 | "IM Q36p3y5ps2 Qr20-FSW Round Bar", 133 | "IM Q36p3y5ps2 Qr24-ISW Round Bar"] 134 | labels = [ 'Qs24ps2p1Qr14', 135 | 'Qs24ps2p1Qr16', 136 | 'Qs36ps2p3Qr20(Sepa)', 137 | 'Qs36ps2p3Qr24(Sepa)', 138 | 'Qs36ps2p3Qr20(Comb)', 139 | 'Qs36ps2p3Qr24(Comb)'] 140 | data_folder_names = ['DataFolder_p1Qs24ps2Qr14/', 141 | 'DataFolder_p1Qs24ps2Qr16/', 142 | 'Single-Layer-FSW-Separate-Winding/', 143 | 'Single-Layer-ISW-Separate-Winding/', 144 | 'Single-Layer-FSW/', 145 | 'Single-Layer-ISW/'] 146 | markers = [ '$1$', '$2$', '$3$', '$4$', '$5$', '$6$'] 147 | plotting_setting = None 148 | select_fea_config_dict = "#019 JMAG IM Nine Variables" 149 | 150 | 151 | for select_spec, data_folder_name, marker, label in zip(specs, 152 | data_folder_names, 153 | markers, 154 | labels 155 | ): 156 | 157 | if marker is None: 158 | continue 159 | 160 | path_to_archive = folder_of_collection + data_folder_name + select_spec.replace(' ', '_') + '/' 161 | 162 | output_dir, spec_input_dict, fea_config_dict = load_settings(select_spec, select_fea_config_dict, folder_of_collection+data_folder_name, bool_post_processing = True) 163 | 164 | spec = pyrhonen_procedure_as_function.desgin_specification(**spec_input_dict) 165 | if 'SM' in select_spec: 166 | spec.acm_template = spec.build_pmsm_template(fea_config_dict, spec_input_dict, im_template=None) 167 | elif 'IM' in select_spec: 168 | # This template generation code is copied from main.py, and it needs some revision to reduce the codes. 169 | print(spec.build_name()) 170 | spec.bool_bad_specifications = spec.pyrhonen_procedure() 171 | for k,v in spec.spec_geometry_dict.items(): 172 | print(k+':', v) 173 | print(spec.build_name()) # rebuild for new guess air gap flux density # TODO:自动修正转子电流密度的设置值? 174 | import population 175 | spec.acm_template = population.bearingless_induction_motor_design(spec_input_dict, spec.spec_derive_dict, spec.spec_geometry_dict, fea_config_dict) 176 | else: 177 | raise 178 | 179 | import acm_designer 180 | global ad 181 | ad = acm_designer.acm_designer(select_spec, spec_input_dict, select_fea_config_dict, fea_config_dict, acm_template=spec.acm_template) 182 | 183 | # dummy population 184 | ad.flag_do_not_evaluate_when_init_pop = True 185 | 186 | if 'IM' in select_spec: 187 | ad.bounds_denorm = spec.get_im_classic_bounds(which_filter=fea_config_dict['which_filter']) 188 | ad.bound_filter = spec.bound_filter 189 | otnb = spec.original_template_neighbor_bounds 190 | elif 'PMSM' in select_spec: 191 | ad.bounds_denorm = spec.acm_template.bounds_denorm 192 | # ad.bound_filter = spec.bound_filter 193 | otnb = spec.acm_template.original_template_neighbor_bounds 194 | 195 | ad.counter_fitness_called = 99999 196 | ad.counter_fitness_return = 99999 197 | 198 | __builtins__.ad = ad # share global variable between modules # https://stackoverflow.com/questions/142545/how-to-make-a-cross-module-variable 199 | 200 | 201 | #~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~ 202 | # Collect all data 203 | #~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~ 204 | path_to_target = fea_config_dict['dir.parent'] + folder_of_collection + data_folder_name+select_spec.replace(' ', '_') + '/' # '_Q24p1y9_restart_from_optimal_and_reevaluate_wo_csv/PMSM_Q24p1y9_A/' 205 | 206 | ad.analyzer.output_dir = path_to_target 207 | number_of_chromosome = ad.read_swarm_data(select_spec) 208 | print('number_of_chromosome =', number_of_chromosome) 209 | 210 | # Set the output_dir back right away 211 | # ad.analyzer.output_dir = ad.analyzer.fea_config_dict['dir.parent'] + ad.analyzer.fea_config_dict['run_folder'] 212 | 213 | _swarm_data = ad.swarm_data 214 | _swarm_project_names = ad.swarm_data_container.project_names 215 | 216 | #~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~ 217 | # plot the Pareto front for the archive 218 | #~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~ 219 | if True: 220 | scatter_handle = utility_postprocess.pareto_front_plot_script(ad.swarm_data, fig, ax, marker, label, 221 | fea_config_dict=fea_config_dict, z_filter=60) # z_filter=20 filtered individual that has OC larger than 20 222 | # break 223 | 224 | # plt.show() 225 | # quit() 226 | 227 | fig = utility_postprocess.pareto_front_plot_color_bar_etc(scatter_handle, fig, ax, font, settings=plotting_setting) 228 | 229 | # plt.gca().set_axis_off() 230 | # plt.subplots_adjust(top = 1, bottom = 0, right = 1, left = 0, 231 | # hspace = 0, wspace = 0) 232 | # plt.margins(0,0) 233 | 234 | fig.savefig(fea_config_dict['dir.parent'] + folder_of_collection + r'Figure_Combined_Pareto_Front.pdf', format='pdf', dpi=600) 235 | plt.show() 236 | 237 | 238 | -------------------------------------------------------------------------------- /codes3/Pole-specific_winding_with_neutral_plate_the_design_table_generator.py: -------------------------------------------------------------------------------- 1 | class ValidSet_of_PoleSpecificWindingWithNeutralPlate(object): 2 | def __init__(self, layers): 3 | print('_________________________\nlayers =', layers) # =1 or =2 4 | 5 | # ps in N_Even for single layer winding and k in N for double layer winding 6 | for ps in range(2, MAX_PS, 3-layers): 7 | print('\t' + 'ps =', ps) 8 | 9 | # p = ps +- 1 10 | for p in [ps-1, ps+1]: 11 | print('\t'*2 + f'p = {p:d}', end=' ') 12 | valid_Qr = self.get_ValidSet_of_Qr(QS, p) 13 | print('| Valid Qr set:', valid_Qr) 14 | 15 | # k1 in N < ps 16 | for k1 in range(1, ps): 17 | print('\t'*3 + 'k1 =', k1) 18 | 19 | # c/d = reduced( k1/ps ) 20 | c = Fraction(k1, ps).numerator 21 | d = Fraction(k1, ps).denominator 22 | print('\t'*3 + 'c/d = %d/%d'%(c, d)) 23 | 24 | # k in N_Even for single layer winding and k in N for double layer winding 25 | # list_of_k = [k for k in range(1, MAX_K) if k%2==0] if layers==1 else range(1, MAX_K) # this turns out to be wrong 26 | list_of_k = [k for k in range(1, MAX_K)] 27 | for k in list_of_k: 28 | print('\t'*4 + 'k=%3d'%(k), end='') 29 | 30 | Qr = k*d 31 | print(' ' + 'Qr=%3d'%(Qr), end='') 32 | if Qr not in valid_Qr: 33 | print() 34 | continue 35 | 36 | nl = Qr/2 + 1 if layers == 1 else 0 37 | print(' ' + 'nl=%2d'%(nl), end='') 38 | 39 | y = k*c 40 | print(' ' + 'y=%3d'%(y), end='') 41 | 42 | # This step is core. Pick an m to enforce z=1 (q=z/n). 43 | m = Fraction(Qr, 2*p).numerator 44 | n = Fraction(Qr, 2*p).denominator 45 | print(' ' + 'm/n=%3d/%d'%(m,n), end='') 46 | 47 | # fractional slot winding? 48 | print(' ISW ' if n ==1 else ' _FSW', end='') 49 | 50 | # SPP 51 | print(' ' + 'q=z/n=%d/%d'%(Fraction(Qr,2*p*m).numerator, Fraction(Qr,2*p*m).denominator), end='') 52 | 53 | # coil pitch 54 | p_aster_gamma = p*y/Qr*360 55 | while p_aster_gamma > 360: p_aster_gamma -= 360 56 | if p_aster_gamma > 180: p_aster_gamma = 360 - p_aster_gamma 57 | print(' ' + 'p*gamma=%3g'%(p_aster_gamma), end='') 58 | 59 | # winding factor = pitch factor because z=1, i.e., no distribution, i.e., one coil per coil group 60 | gamma = y/Qr * 2*math.pi 61 | print(' ' + 'kw_h = sin(h*gamma/2) =', end='') 62 | for h in range(1, 10): 63 | kw_h = math.sin(h*gamma/2) 64 | print(f'{kw_h:.2f}', end=', ') 65 | print('...') 66 | 67 | def get_ValidSet_of_Qr(self, Qs, p): 68 | valid_Qr = [] 69 | for Qr in range(2, 2*Qs, 2): 70 | if abs(Qs - Qr) == 2 \ 71 | or abs(Qs - Qr) == 2*p+1 \ 72 | or abs(Qs - Qr) == 2*p-1 \ 73 | or Qr == Qs \ 74 | or Qr == 0.5*Qs \ 75 | or Qr == 2*Qs: 76 | # or abs(Qs - Qr) == 2*p+2 \ 77 | # or abs(Qs - Qr) == 2*p-2 \ 78 | # or Qr > 1.25*Qs \ 79 | # or Qr % (6*p) == 0 \ 80 | # or (Qr+2*p) % (6*p) == 0 \ 81 | # or (Qr-2*p) % (6*p) == 0 \ 82 | # or abs(Qr-Qs) == 2*p \ 83 | # or abs(Qr-2*Qs) == 2*p \ 84 | # or abs(Qr-Qs) == p \ 85 | # or abs(Qr-0.5*Qs) == p: 86 | continue 87 | else: 88 | valid_Qr.append(Qr) 89 | return valid_Qr 90 | 91 | def get_design(self, layers, ps, p, k1, k): 92 | # c/d = reduced( k1/ps ) 93 | self.c = c = Fraction(k1, ps).numerator 94 | self.d = d = Fraction(k1, ps).denominator 95 | print('c/d\t= %d/%d'%(c, d)) 96 | 97 | self.Qr = Qr = k*d 98 | print(f'Qr\t= {Qr}') 99 | print(f'p\t= {p}') 100 | print(f'ps\t= {ps}') 101 | print(f'k1\t= {k1}') 102 | print(f'k\t= {k}') 103 | print(f't\t= {math.gcd(Qr,p)}') 104 | 105 | self.nl = Qr/2 + 1 if layers == 1 else 0 106 | print('nl\t= %2d'%(self.nl)) 107 | 108 | y = k*c 109 | self.coil_pitch_y = coil_pitch_y = y 110 | print('y\t= %3d'%(y)) 111 | 112 | # This step is core. Pick an m to enforce z=1 (q=z/n). 113 | self.m = m = Fraction(Qr, 2*p).numerator 114 | self.n = n = Fraction(Qr, 2*p).denominator 115 | print('m/n\t= %3d/%d'%(m,n), '(this is consistent with Pyrhonen@(2.83): m cannot be multiples of n.)') 116 | 117 | # fractional slot winding? 118 | print('ISW' if n==1 else 'FSW') 119 | 120 | # SPP 121 | self.q = Qr/(2*p*m) 122 | self.z = z = Fraction(Qr,2*p*m).numerator 123 | self.n = n = Fraction(Qr,2*p*m).denominator 124 | print(f'q=z/n={z:d}/{n:d} = {Qr:d}/(2*{p}*{m})') 125 | 126 | # coil pitch 127 | p_aster_gamma = p*y/Qr*360 128 | while p_aster_gamma > 360: p_aster_gamma -= 360 129 | if p_aster_gamma > 180: p_aster_gamma = 360 - p_aster_gamma 130 | print('p*gamma\t=%3g'%(p_aster_gamma)) 131 | 132 | # winding factor = pitch factor because z=1, i.e., no distribution, i.e., one coil per coil group 133 | self.gamma = gamma = y/Qr * 2*math.pi 134 | print('kw_h = sin(h*gamma/2) =', end='') 135 | for h in range(1, 10): 136 | kw_h = math.sin(h*gamma/2) 137 | print(f'{kw_h:.2f}', end=', ') 138 | print('...') 139 | 140 | 141 | if layers == 1: 142 | number_of_coils = Qr/2 # each coil takes up two slots. 143 | elif layers == 2: 144 | number_of_coils = Qr 145 | self.number_of_coils = number_of_coils 146 | print('number_of_coils =', number_of_coils) 147 | print('number of coils per phase =', number_of_coils / m) 148 | 149 | 150 | # --------------------------------------------------- Tentative winding layout 151 | char_bias = 97 152 | sU = '' 153 | sL = '' 154 | self.pairs = [] 155 | if y*2 == Qr and layers==1: 156 | for i in range(y): 157 | sU += ' | ' + chr(char_bias+i) 158 | self.pairs.append( (i+1, i+y+1) ) 159 | for ind, _ in enumerate(range(y, Qr)): 160 | sU += ' | ' + chr(char_bias+ind) 161 | elif layers==2 and (y*3 == Qr or y*5 == Qr): 162 | for i in range(Qr-y): 163 | sU += ' | ' + chr(char_bias+i) 164 | # sL += ' | ' + chr(char_bias+Qr+i-y) 165 | self.pairs.append( (i+1, i+y+1) ) 166 | for i in range(Qr-y, Qr): 167 | sU += ' | ' + chr(char_bias+i) 168 | # sL += ' | ' + chr(char_bias+i+y) 169 | self.pairs.append( (i+1, i+y-Qr+1) ) 170 | else: 171 | raise Exception('Not implemented.') 172 | print(f'----\nWinding Layout (m={m:d} phases):') 173 | print("".join([f' |{i+1:2d}' for i in range(Qr)])) 174 | print(sU) 175 | print(sL) 176 | print('\n'*3) 177 | print('----------Winding layout transcript used in winding_layout.py is as follows:') 178 | print(r''' 179 | if Qr == %d \ 180 | and p == %d \ 181 | and ps == %d \ 182 | and coil_pitch_y == %d:'''%(Qr,p,ps,coil_pitch_y), end='\n\t\t\tself.pairs = ') 183 | if layers == 2: 184 | print(design.reduce_to_single_layer()) 185 | else: 186 | print(self.pairs) 187 | 188 | def reduce_to_single_layer(self): 189 | list_groups = [] 190 | # print('\n|||', self.pairs) 191 | # quit() 192 | for _, target in enumerate(self.pairs): 193 | bool_continue = False 194 | for group in list_groups: 195 | # print(target[0], 'in', group, '?') # 11 in (1, 11, 21) ? 196 | if target[0] in group: 197 | bool_continue = True # avoid to include (1, 11, 21) and (11, 21, 1) at the same time 198 | if bool_continue == True: 199 | continue 200 | 201 | # print(target, end=' | ') 202 | bool_appended = False 203 | group = list(target) # [1, 11] | [11, 21] 204 | for i in range(_+1, len(self.pairs)): 205 | pair = self.pairs[i] # (2, 12), (11, 21) | (1, 11) 206 | if pair[0] in group: # 2, 11 | 1 207 | if pair[1] not in group: 208 | group.append(pair[1]) 209 | bool_appended = True 210 | if pair[1] in group: # 12, 21 | 11 211 | if pair[0] not in group: 212 | group.append(pair[0]) 213 | bool_appended = True 214 | # print(group) 215 | if bool_appended: 216 | list_groups.append(tuple(group)) 217 | 218 | # print('Before:', self.pairs) 219 | # print('After:', list_groups) 220 | return list_groups 221 | 222 | if __name__ == '__main__': 223 | layers = 1; QS = 36 224 | layers = 1; QS = 24 225 | layers = 2; QS = 24 226 | # layers = 2; QS = 12 227 | 228 | print(f'For Qs={QS:d}, find valid set of pole specific winding with neutral plate.') 229 | from fractions import Fraction 230 | import math 231 | MAX_K = 20+1 232 | MAX_PS = 5+1 233 | 234 | 235 | 236 | 237 | import os, sys 238 | # sys.stdout = open(os.devnull, "w") 239 | design = ValidSet_of_PoleSpecificWindingWithNeutralPlate(layers=layers) 240 | sys.stdout = sys.__stdout__ 241 | 242 | # print('\n'+'~*'*40) 243 | # design.get_design(layers=1, ps=2, p=3, k1=1, k=12) 244 | # print('\n'+'~*'*40) 245 | # design.get_design(layers=1, ps=2, p=3, k1=1, k=10) 246 | 247 | # print('\n'+'~*'*40) 248 | # design.get_design(layers=2, ps=3, p=2, k1=1, k=10) 249 | 250 | # print('\n'+'~*'*40) 251 | # design.get_design(layers=2, ps=3, p=2, k1=1, k=6) 252 | 253 | print('\n'+'~*'*40) 254 | design.get_design(layers=2, ps=5, p=4, k1=1, k=2) 255 | 256 | -------------------------------------------------------------------------------- /codes3/Pole_specific_winding_with_neutral_plate_the_design_table_generator.py: -------------------------------------------------------------------------------- 1 | from fractions import Fraction 2 | import math 3 | import os, sys 4 | MAX_K = 20+1 5 | MAX_PS = 4+1 6 | class ValidSet_of_PoleSpecificWindingWithNeutralPlate(object): 7 | def __init__(self, layers, QS): 8 | print('_________________________\nlayers =', layers) # =1 or =2 9 | 10 | # ps in N_Even for single layer winding and k in N for double layer winding 11 | for ps in range(2, MAX_PS, 3-layers): 12 | print('\t' + 'ps =', ps) 13 | 14 | # p = ps +- 1 15 | for p in [ps-1, ps+1]: 16 | print('\t'*2 + f'p = {p:d}', end=' ') 17 | valid_Qr = self.get_ValidSet_of_Qr(QS, p) 18 | print('| Valid Qr set:', valid_Qr) 19 | 20 | # k1 in N < ps 21 | for k1 in range(1, ps): 22 | print('\t'*3 + 'k1 =', k1) 23 | 24 | # c/d = reduced( k1/ps ) 25 | c = Fraction(k1, ps).numerator 26 | d = Fraction(k1, ps).denominator 27 | print('\t'*3 + 'c/d = %d/%d'%(c, d)) 28 | 29 | # k in N_Even for single layer winding and k in N for double layer winding 30 | # list_of_k = [k for k in range(1, MAX_K) if k%2==0] if layers==1 else range(1, MAX_K) # this turns out to be wrong 31 | list_of_k = [k for k in range(1, MAX_K)] 32 | for k in list_of_k: 33 | print('\t'*4 + 'k=%3d'%(k), end='') 34 | 35 | Qr = k*d 36 | print(' ' + 'Qr=%3d'%(Qr), end='') 37 | if Qr not in valid_Qr: 38 | print() 39 | continue 40 | 41 | nl = Qr/2 + 1 if layers == 1 else 0 42 | print(' ' + 'nl=%2d'%(nl), end='') 43 | 44 | y = k*c 45 | print(' ' + 'y=%3d'%(y), end='') 46 | 47 | # This step is core. Pick an m to enforce z=1 (q=z/n). 48 | m = Fraction(Qr, 2*p).numerator 49 | n = Fraction(Qr, 2*p).denominator 50 | print(' ' + 'm/n=%3d/%d'%(m,n), end='') 51 | 52 | # fractional slot winding? 53 | print(' ISW ' if n ==1 else ' _FSW', end='') 54 | if n!=1: 55 | if n%2==1: 56 | print(' First grade. ', end='') 57 | elif n%2==0: 58 | print(' Second grade.', end='') 59 | else: 60 | print(' ', end='') 61 | 62 | # SPP 63 | print(' ' + 'q=z/n=%d/%d'%(Fraction(Qr,2*p*m).numerator, Fraction(Qr,2*p*m).denominator), end='') 64 | 65 | # coil pitch 66 | p_aster_gamma = p*y/Qr*360 67 | while p_aster_gamma > 360: p_aster_gamma -= 360 68 | if p_aster_gamma > 180: p_aster_gamma = 360 - p_aster_gamma 69 | print(' ' + 'p*gamma=%3g'%(p_aster_gamma), end='') 70 | 71 | # winding factor = pitch factor because z=1, i.e., no distribution, i.e., one coil per coil group 72 | gamma = y/Qr * 2*math.pi 73 | print(' ' + 'kw_h = sin(h*gamma/2) =', end='') 74 | for h in range(1, 10): 75 | kw_h = math.sin(h*gamma/2) 76 | print(f'{kw_h:.2f}', end=', ') 77 | print('...') 78 | 79 | def get_ValidSet_of_Qr(self, Qs, p): 80 | valid_Qr = [] 81 | for Qr in range(2, 2*Qs, 2): 82 | if abs(Qs - Qr) == 2 \ 83 | or abs(Qs - Qr) == 2*p+1 \ 84 | or abs(Qs - Qr) == 2*p-1 \ 85 | or abs(Qs - Qr) == 2*p+2 \ 86 | or abs(Qs - Qr) == 2*p-2 \ 87 | or Qr == Qs \ 88 | or Qr == 0.5*Qs \ 89 | or Qr == 2*Qs: 90 | # or Qr > 1.25*Qs \ 91 | # or Qr % (6*p) == 0 \ 92 | # or (Qr+2*p) % (6*p) == 0 \ 93 | # or (Qr-2*p) % (6*p) == 0 \ 94 | # or abs(Qr-Qs) == 2*p \ 95 | # or abs(Qr-2*Qs) == 2*p \ 96 | # or abs(Qr-Qs) == p \ 97 | # or abs(Qr-0.5*Qs) == p: 98 | continue 99 | else: 100 | valid_Qr.append(Qr) 101 | return valid_Qr 102 | 103 | def get_design(self, layers, ps, p, k1, k): 104 | self.p = p 105 | self.ps = ps 106 | self.k1 = k1 107 | self.k = k 108 | 109 | # c/d = reduced( k1/ps ) 110 | self.c = c = Fraction(k1, ps).numerator 111 | self.d = d = Fraction(k1, ps).denominator 112 | print('c/d\t= %d/%d'%(c, d)) 113 | 114 | self.Qr = Qr = k*d 115 | self.t = t = math.gcd(Qr,p) 116 | self.Qr_prime = Qr / t 117 | print(f'Qr\t= {Qr}') 118 | print(f'p\t= {p}') 119 | print(f'ps\t= {ps}') 120 | print(f'k1\t= {k1}') 121 | print(f'k\t= {k}') 122 | print(f't\t= {t}') 123 | 124 | self.nl = nl = Qr/2 + 1 if layers == 1 else 0 125 | print('nl\t= %2d'%(nl)) 126 | 127 | y = k*c 128 | self.coil_pitch_y = coil_pitch_y = y 129 | print('y\t= %3d'%(y)) 130 | 131 | # This step is core. Pick an m to enforce z=1 (q=z/n). 132 | self.m = m = Fraction(Qr, 2*p).numerator 133 | self.n = n = Fraction(Qr, 2*p).denominator 134 | print('m/n\t= %3d/%d'%(m,n), '(this is consistent with Pyrhonen@(2.83): m cannot be multiples of n.)') 135 | 136 | # fractional slot winding? 137 | print('ISW' if n==1 else 'FSW') 138 | 139 | # SPP 140 | self.q = Qr/(2*p*m) 141 | self.z = z = Fraction(Qr,2*p*m).numerator 142 | self.n = n = Fraction(Qr,2*p*m).denominator 143 | print(f'q=z/n={z:d}/{n:d} = {Qr:d}/(2*{p}*{m})') 144 | 145 | # coil pitch 146 | p_aster_gamma = p*y/Qr*360 147 | while p_aster_gamma > 360: p_aster_gamma -= 360 148 | if p_aster_gamma > 180: p_aster_gamma = 360 - p_aster_gamma 149 | print('p*gamma\t=%3g'%(p_aster_gamma)) 150 | 151 | # winding factor = pitch factor because z=1, i.e., no distribution, i.e., one coil per coil group 152 | self.gamma = gamma = y/Qr * 2*math.pi 153 | print('kw_h = sin(h*gamma/2) =', end='') 154 | kw_at_h = dict() 155 | for h in range(1, 20): 156 | kw_h = math.sin(h*gamma/2) 157 | kw_at_h[h] = kw_h 158 | print(f'{kw_h:.2f}', end=', ') 159 | self.kw_at_h = kw_at_h 160 | print('...') 161 | 162 | 163 | if layers == 1: 164 | number_of_coils = Qr/2 # each coil takes up two slots. 165 | elif layers == 2: 166 | number_of_coils = Qr 167 | self.number_of_coils = number_of_coils 168 | print('number_of_coils =', number_of_coils) 169 | print('number of coils per phase =', number_of_coils / m) 170 | 171 | 172 | # --------------------------------------------------- Tentative winding layout 173 | char_bias = 97 174 | sU = '' 175 | sL = '' 176 | self.pairs = [] 177 | if y*2 == Qr and layers==1: 178 | for i in range(y): 179 | sU += ' | ' + chr(char_bias+i) 180 | self.pairs.append( (i+1, i+y+1) ) 181 | for ind, _ in enumerate(range(y, Qr)): 182 | sU += ' | ' + chr(char_bias+ind) 183 | elif y*3 == Qr and layers==2: 184 | for i in range(Qr-y): 185 | sU += ' | ' + chr(char_bias+i) 186 | # sL += ' | ' + chr(char_bias+Qr+i-y) 187 | self.pairs.append( (i+1, i+y+1) ) 188 | for i in range(Qr-y, Qr): 189 | sU += ' | ' + chr(char_bias+i) 190 | # sL += ' | ' + chr(char_bias+i+y) 191 | self.pairs.append( (i+1, i+y-Qr+1) ) 192 | else: 193 | raise Exception('Not implemented.') 194 | print(f'----\nWinding Layout (m={m:d} phases):') 195 | print("".join([f' |{i+1:2d}' for i in range(Qr)])) 196 | print(sU) 197 | print(sL) 198 | print('\n'*3) 199 | print('----------Winding layout transcript used in winding_layout.py is as follows:') 200 | print(r''' 201 | if Qr == %d \ 202 | and p == %d \ 203 | and ps == %d \ 204 | and coil_pitch_y == %d:'''%(Qr,p,ps,coil_pitch_y), end='\n\t\t\tself.pairs = ') 205 | if layers == 2: 206 | print(self.reduce_to_single_layer()) 207 | else: 208 | print(self.pairs) 209 | 210 | def reduce_to_single_layer(self): 211 | list_groups = [] 212 | # print('\n|||', self.pairs) 213 | # quit() 214 | for _, target in enumerate(self.pairs): 215 | bool_continue = False 216 | for group in list_groups: 217 | # print(target[0], 'in', group, '?') # 11 in (1, 11, 21) ? 218 | if target[0] in group: 219 | bool_continue = True # avoid to include (1, 11, 21) and (11, 21, 1) at the same time 220 | if bool_continue == True: 221 | continue 222 | 223 | # print(target, end=' | ') 224 | bool_appended = False 225 | group = list(target) # [1, 11] | [11, 21] 226 | for i in range(_+1, len(self.pairs)): 227 | pair = self.pairs[i] # (2, 12), (11, 21) | (1, 11) 228 | if pair[0] in group: # 2, 11 | 1 229 | if pair[1] not in group: 230 | group.append(pair[1]) 231 | bool_appended = True 232 | if pair[1] in group: # 12, 21 | 11 233 | if pair[0] not in group: 234 | group.append(pair[0]) 235 | bool_appended = True 236 | # print(group) 237 | if bool_appended: 238 | list_groups.append(tuple(group)) 239 | 240 | # print('Before:', self.pairs) 241 | # print('After:', list_groups) 242 | return list_groups 243 | 244 | if __name__ == '__main__': 245 | layers = 1; QS = 36 246 | layers = 1; QS = 24 247 | layers = 2; QS = 24 248 | layers = 2; QS = 18 249 | layers = 2; QS = 36 250 | 251 | print(f'For Qs={QS:d}, find valid set of pole specific winding with neutral plate.') 252 | from fractions import Fraction 253 | import math 254 | MAX_K = 20+1 255 | MAX_PS = 4+1 256 | 257 | 258 | 259 | 260 | import os, sys 261 | # sys.stdout = open(os.devnull, "w") 262 | design = ValidSet_of_PoleSpecificWindingWithNeutralPlate(layers=layers, QS=QS) 263 | sys.stdout = sys.__stdout__ 264 | 265 | # print('\n'+'~*'*40) 266 | # design.get_design(layers=1, ps=2, p=3, k1=1, k=12) 267 | # print('\n'+'~*'*40) 268 | # design.get_design(layers=1, ps=2, p=3, k1=1, k=10) 269 | 270 | print('\n'+'~*'*40) 271 | design.get_design(layers=2, ps=3, p=2, k1=1, k=10) 272 | # ps.get_design(layers=layers, ps=wd.ps, p=wd.p, k1=k1, k=k) 273 | 274 | 275 | 276 | -------------------------------------------------------------------------------- /codes3/Problem_BearinglessInductionDesign.py: -------------------------------------------------------------------------------- 1 | import builtins 2 | if hasattr(builtins, 'ad'): 3 | print('Global variables are shared between modules...') 4 | else: 5 | raise Exception('Please add global variable (address) "ad" to module __builtins__.') 6 | print(builtins.ad) 7 | print(ad) 8 | print(ad.counter_fitness_called) 9 | print(ad.counter_fitness_return) 10 | 11 | import logging, os, shutil 12 | import numpy as np 13 | from acm_designer import get_bad_fintess_values 14 | import utility 15 | import pywintypes 16 | class Problem_BearinglessInductionDesign(object): 17 | 18 | # Define objectives 19 | def fitness(self, x): 20 | global ad 21 | 22 | if ad.flag_do_not_evaluate_when_init_pop == True: 23 | return [0, 0, 0] 24 | 25 | if ad.counter_fitness_called == ad.counter_fitness_return: 26 | ad.counter_fitness_called += 1 27 | else: 28 | # This is not reachable 29 | raise Exception('ad.counter_fitness_called') 30 | print('Call fitness: %d, %d'%(ad.counter_fitness_called, ad.counter_fitness_return)) 31 | 32 | # 不要标幺化了!统一用真的bounds,见get_bounds() 33 | x_denorm = x 34 | 35 | # evaluate x_denorm via FEA tools 36 | counter_loop = 0 37 | stuck_at = 0 38 | while True: 39 | if ad.fea_config_dict['bool_re_evaluate']: 40 | if ad.counter_fitness_return >= len(ad.solver.swarm_data): 41 | quit() 42 | x_denorm = ad.solver.swarm_data[ad.counter_fitness_return][:-3] 43 | print(ad.solver.swarm_data[ad.counter_fitness_return]) 44 | 45 | if stuck_at < ad.counter_fitness_called: 46 | stuck_at = ad.counter_fitness_called 47 | counter_loop = 0 # reset 48 | if stuck_at == ad.counter_fitness_called: 49 | counter_loop += 1 50 | if counter_loop > 3: 51 | raise Exception('Abort the optimization. Three attemps to evaluate the design have all failed for individual #%d'%(ad.counter_fitness_called)) 52 | 53 | # try: 54 | if True: 55 | acm_variant = ad.evaluate_design_json_wrapper(ad.spec.acm_template, x_denorm, ad.counter_fitness_called, counter_loop=counter_loop) 56 | 57 | cost_function, f1, f2, f3, FRW, \ 58 | normalized_torque_ripple, \ 59 | normalized_force_error_magnitude, \ 60 | force_error_angle = acm_variant.results_for_optimization 61 | 62 | # remove folder .jfiles to save space (we have to generate it first in JMAG Designer to have field data and voltage profiles) 63 | if ad.solver.folder_to_be_deleted is not None and os.path.isdir(ad.solver.folder_to_be_deleted): 64 | try: 65 | shutil.rmtree(ad.solver.folder_to_be_deleted) # .jfiles directory 66 | except PermissionError as error: 67 | print(error) 68 | print('Skip deleting this folder...') 69 | 70 | # update to be deleted when JMAG releases the use 71 | ad.solver.folder_to_be_deleted = ad.solver.expected_project_file[:-5]+'jfiles' 72 | 73 | # except utility.ExceptionBadNumberOfParts as error: 74 | # print(type(error), str(error)) 75 | # print("Detail: {}".format(error.payload)) 76 | # f1, f2, f3 = get_bad_fintess_values() 77 | # utility.send_notification(ad.solver.fea_config_dict['pc_name'] + '\n\nExceptionBadNumberOfParts:' + str(error) + '\n'*3 + "Detail: {}".format(error.payload)) 78 | # break 79 | 80 | # except (utility.ExceptionReTry, pywintypes.com_error) as error: 81 | # print(type(error), error) 82 | 83 | # msg = 'FEA tool failed for individual #%d: attemp #%d.'%(ad.counter_fitness_called, counter_loop) 84 | # logger = logging.getLogger(__name__) 85 | # logger.error(msg) 86 | # print(msg) 87 | 88 | # # if False: 89 | # # msg = 'Removing all files for individual #%d and try again...'%(ad.counter_fitness_called) 90 | # # logger.error(msg) 91 | # # print(msg) 92 | # # try: 93 | # # # turn off JMAG Designer 94 | # # # try: 95 | # # # ad.solver.app.Quit() 96 | # # # except: 97 | # # # print('I think there is no need to Quit the app') 98 | # # ad.solver.app = None 99 | 100 | # # # JMAG files 101 | # # # os.remove(ad.solver.expected_project_file) # .jproj 102 | # # # shutil.rmtree(ad.solver.expected_project_file[:-5]+'jfiles') # .jfiles directory # .jplot file in this folder will be used by JSOL softwares even JMAG Designer is closed. 103 | 104 | # # # FEMM files 105 | # # if os.path.exists(ad.solver.femm_output_file_path): 106 | # # os.remove(ad.solver.femm_output_file_path) # .csv 107 | # # if os.path.exists(ad.solver.femm_output_file_path[:-3]+'fem'): 108 | # # os.remove(ad.solver.femm_output_file_path[:-3]+'fem') # .fem 109 | # # for file in os.listdir(ad.solver.dir_femm_temp): 110 | # # if 'femm_temp_' in file or 'femm_found' in file: 111 | # # os.remove(ad.solver.dir_femm_temp + file) 112 | 113 | # # except Exception as e2: 114 | # # utility.send_notification(ad.solver.fea_config_dict['pc_name'] + '\n\nException 1:' + str(error) + '\n'*3 + 'Exception 2:' + str(e2)) 115 | # # raise e2 116 | # from time import sleep 117 | # print('\n\n\nSleep for 3 sec and continue.') 118 | # sleep(3) 119 | 120 | # continue 121 | 122 | # except AttributeError as error: 123 | # print(type(error), str(error)) 124 | # print("Detail: {}".format(error.payload)) 125 | 126 | # msg = 'FEA tool failed for individual #%d: attemp #%d.'%(ad.counter_fitness_called, counter_loop) 127 | # logger = logging.getLogger(__name__) 128 | # logger.error(msg) 129 | # print(msg) 130 | 131 | # if 'designer.Application' in str(error): 132 | # from time import sleep 133 | # print('\n\n\nSleep for 3 sec and continue.') 134 | # sleep(3) 135 | 136 | # continue 137 | # else: 138 | # raise error 139 | 140 | # except Exception as e: # raise and need human inspection 141 | 142 | # print('-'*40 + 'Unexpected error is caught.') 143 | # print(type(error), str(e)) 144 | # utility.send_notification(ad.solver.fea_config_dict['pc_name'] + '\n\nUnexpected expection:' + str(e)) 145 | # raise e 146 | 147 | # else: 148 | # break 149 | break 150 | 151 | # - Torque per Rotor Volume 152 | f1 #= - ad.spec.required_torque / rated_rotor_volume 153 | # - Efficiency @ Rated Power 154 | f2 #= - rated_efficiency 155 | # Ripple Performance (Weighted Sum) 156 | f3 #= sum(list_weighted_ripples) 157 | print('f1,f2,f3:',f1,f2,f3) 158 | 159 | # Constraints (Em<0.2 and Ea<10 deg): 160 | if ad.spec.acm_template.fea_config_dict['moo.apply_constraints']==True: 161 | # if abs(normalized_torque_ripple)>=0.2 or abs(normalized_force_error_magnitude) >= 0.2 or abs(force_error_angle) > 10: 162 | if abs(normalized_torque_ripple)>=0.3 or abs(normalized_force_error_magnitude) >= 0.3 or abs(force_error_angle) > 10 or FRW < 0.75: 163 | print('Constraints are violated:') 164 | if abs(normalized_torque_ripple)>=0.3: 165 | print('\tabs(normalized_torque_ripple)>=0.3') 166 | if abs(normalized_force_error_magnitude) >= 0.3: 167 | print('\tabs(normalized_force_error_magnitude) >= 0.3') 168 | if abs(force_error_angle) > 10: 169 | print('\tabs(force_error_angle) > 10') 170 | if FRW < 0.75: 171 | print('\tFRW < 0.75') 172 | f1, f2, f3 = get_bad_fintess_values() 173 | print('f1,f2,f3:',f1,f2,f3) 174 | 175 | ad.counter_fitness_return += 1 176 | print('Fitness: %d, %d\n----------------'%(ad.counter_fitness_called, ad.counter_fitness_return)) 177 | return [f1, f2, f3] 178 | 179 | # Return number of objectives 180 | def get_nobj(self): 181 | return 3 182 | 183 | # Return bounds of decision variables (a.k.a. chromosome) 184 | def get_bounds(self): 185 | global ad 186 | 187 | # denormalize the normalized chromosome x to x_denorm 188 | min_b, max_b = np.asarray(ad.bounds_denorm).T 189 | # diff = np.fabs(min_b - max_b) 190 | # x_denorm = min_b + x * diff 191 | 192 | # print(min_b.tolist(), max_b.tolist()) 193 | # print(([0]*7, [1]*7)) 194 | # quit() 195 | return ( min_b.tolist(), max_b.tolist() ) 196 | # return ([0]*7, [1]*7) 197 | 198 | # Return function name 199 | def get_name(self): 200 | return "Bearingless Induction Motor Design" 201 | 202 | import pygmo as pg 203 | def get_prob(): 204 | udp = Problem_BearinglessInductionDesign() 205 | prob = pg.problem(udp) 206 | return udp, prob 207 | 208 | print('Module Problem_BearinglessInductionDesign is imported...') 209 | 210 | -------------------------------------------------------------------------------- /codes3/Problem_BearinglessSynchronousDesign.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from acm_designer import get_bad_fintess_values 3 | import utility 4 | import logging, os, shutil 5 | import pywintypes 6 | import builtins 7 | if hasattr(builtins, 'ad'): 8 | print('[Problem_BlessSyn] Global variable ad is shared between modules as we cannot pass new argument to udp class.') 9 | else: 10 | raise Exception('[Problem_BlessSyn] Please add global variable (address) "ad" to module __builtins__.') 11 | 12 | # print('[Problem_BlessSyn]', builtins.ad) 13 | 14 | # print('[Problem_BlessSyn]', ad) 15 | # print('[Problem_BlessSyn]', ad.counter_fitness_called) 16 | # print('[Problem_BlessSyn]', ad.counter_fitness_return) 17 | 18 | class Problem_BearinglessSynchronousDesign(object): 19 | 20 | # Define objectives 21 | def fitness(self, x): 22 | global ad 23 | 24 | if ad.flag_do_not_evaluate_when_init_pop == True: 25 | return [0, 0, 0] 26 | 27 | if ad.counter_fitness_called == ad.counter_fitness_return: 28 | ad.counter_fitness_called += 1 29 | else: 30 | # This is not reachable 31 | raise Exception(f'ad.counter_fitness_called = {ad.counter_fitness_called} != ad.counter_fitness_return = {ad.counter_fitness_return}!!!') 32 | print('[Problem_BlessSyn] Call fitness: %d, %d'%(ad.counter_fitness_called, ad.counter_fitness_return)) 33 | 34 | # 不要标幺化了!统一用真的bounds,见get_bounds() 35 | x_denorm = x 36 | 37 | # evaluate x_denorm via FEA tools 38 | counter_loop = 0 39 | stuck_at = 0 40 | while True: 41 | if 'bool_re_evaluate' in ad.fea_config_dict.keys() and ad.fea_config_dict['bool_re_evaluate']: 42 | if ad.counter_fitness_return >= len(ad.solver.swarm_data): 43 | raise Exception('ad.counter_fitness_return >= len(ad.solver.swarm_data)') 44 | quit() 45 | x_denorm = ad.solver.swarm_data[ad.counter_fitness_return][:-3] 46 | print('[Problem_BlessSyn]', ad.solver.swarm_data[ad.counter_fitness_return]) 47 | 48 | if stuck_at < ad.counter_fitness_called: 49 | stuck_at = ad.counter_fitness_called 50 | counter_loop = 0 # reset 51 | if stuck_at == ad.counter_fitness_called: 52 | counter_loop += 1 53 | 54 | # if True: 55 | try: 56 | acm_variant = ad.evaluate_design_json_wrapper(ad.acm_template, x_denorm, ad.counter_fitness_called, counter_loop=counter_loop) 57 | cost_function, f1, f2, f3, FRW, \ 58 | normalized_torque_ripple, \ 59 | normalized_force_error_magnitude, \ 60 | force_error_angle = acm_variant.results_for_optimization 61 | 62 | # For JMAG, remove folder ".jfiles" to save space (we have to generate it first in JMAG Designer to have field data and voltage profiles) 63 | if 'JMAG' in ad.select_fea_config_dict: 64 | if ad. folder_to_be_deleted is not None and os.path.isdir(ad. folder_to_be_deleted): 65 | try: 66 | shutil.rmtree(ad. folder_to_be_deleted) # .jfiles directory 67 | except PermissionError as error: 68 | print(error) 69 | print('Skip deleting this folder...') 70 | # update to be deleted when JMAG releases the use 71 | ad. folder_to_be_deleted = ad. expected_project_file[:-5]+'jfiles' 72 | 73 | except KeyboardInterrupt as error: 74 | raise error 75 | 76 | # except utility.ExceptionReTry as error: # The copy region target is not found 77 | # print(str(error)) 78 | # print('CJH: "the ind***TranPMSM_torque.csv is not found" means the mesher or the solver has failed. For now, simply consider it to be bad design.') 79 | # f1, f2, f3 = get_bad_fintess_values(machine_type='PMSM') 80 | # logger = logging.getLogger(__name__) 81 | # logger.error(str(error)) 82 | # break 83 | 84 | except utility.ExceptionBadNumberOfParts as error: 85 | print('ExceptionBadNumberOfParts captured:', str(error)) 86 | # print("Detail: {}".format(error.payload)) 87 | # f1, f2, f3 = get_bad_fintess_values(machine_type='PMSM') 88 | # f1, f2, f3 = get_bad_fintess_values(machine_type='CPPM') 89 | f1, f2, f3 = get_bad_fintess_values(machine_type='CSPPM') 90 | # utility.send_notification(ad.solver.fea_config_dict['pc_name'] + '\n\nExceptionBadNumberOfParts:' + str(error) + '\n'*3) 91 | raise error 92 | 93 | except pywintypes.com_error as error: 94 | print(error) 95 | print('The call to JMAG has failed. Restart?') 96 | raise error 97 | 98 | except Exception as error: 99 | # if ad.bool_re_evaluate == True: 100 | # print('bool_re_evaluate is True...') 101 | # raise error 102 | 103 | raise error 104 | f1, f2, f3 = get_bad_fintess_values(machine_type='PMSM') 105 | print(str(error)) 106 | logger = logging.getLogger(__name__) 107 | logger.error(str(error)) 108 | break 109 | # except FileNotFoundError as error: # The copy region target is not found 110 | # print(str(error)) 111 | # print('CJH: "the ind***TranPMSM_torque.csv is not found" means the mesher or the solver has failed. For now, simply consider it to be bad design.') 112 | # f1, f2, f3 = get_bad_fintess_values(machine_type='PMSM') 113 | 114 | # except utility.ExceptionBadNumberOfParts as error: 115 | # print(str(error)) 116 | # # print("Detail: {}".format(error.payload)) 117 | # f1, f2, f3 = get_bad_fintess_values(machine_type='PMSM') 118 | # utility.send_notification(ad.solver.fea_config_dict['pc_name'] + '\n\nExceptionBadNumberOfParts:' + str(error) + '\n'*3) 119 | # break 120 | 121 | # except (utility.ExceptionReTry, pywintypes.com_error) as error: 122 | # print(error) 123 | 124 | # msg = 'FEA tool failed for individual #%d: attemp #%d.'%(ad.counter_fitness_called, counter_loop) 125 | # logger = logging.getLogger(__name__) 126 | # logger.error(msg) 127 | # print(msg) 128 | 129 | # if counter_loop > 1: # > 1 = two attemps; > 2 = three attemps 130 | # print(error) 131 | # raise Exception('Abort the optimization. Two attemps to evaluate the design have all failed for individual #%d'%(ad.counter_fitness_called)) 132 | # else: 133 | # from time import sleep 134 | # print('\n\n\nSleep for 3 sec and continue.') 135 | # sleep(3) 136 | # continue 137 | 138 | # except AttributeError as error: 139 | # print(str(error)) 140 | # # print("Detail: {}".format(error.payload)) 141 | 142 | # msg = 'FEA tool failed for individual #%d: attemp #%d.'%(ad.counter_fitness_called, counter_loop) 143 | # logger = logging.getLogger(__name__) 144 | # logger.error(msg) 145 | # print(msg) 146 | 147 | # if 'designer.Application' in str(error): 148 | # if counter_loop > 1: 149 | # print(error) 150 | # raise Exception('Abort the optimization. Two attemps to evaluate the design have all failed for individual #%d'%(ad.counter_fitness_called)) 151 | # else: 152 | # from time import sleep 153 | # print('\n\n\nSleep for 3 sec and continue.') 154 | # sleep(3) 155 | # continue 156 | # else: 157 | # raise error 158 | 159 | # except Exception as e: # raise and need human inspection 160 | 161 | # # raise e 162 | # print('-'*40 + 'Unexpected error is caught.') 163 | # print(str(e)) 164 | # utility.send_notification(ad.solver.fea_config_dict['pc_name'] + '\n\nUnexpected expection:' + str(e)) 165 | # raise e 166 | pass 167 | else: 168 | # - Price 169 | f1 170 | # - Efficiency @ Rated Power 171 | f2 172 | # Ripple Performance (Weighted Sum) 173 | f3 174 | print('f1,f2,f3:',f1,f2,f3) 175 | 176 | if ad.acm_template.fea_config_dict['moo.apply_constraints']==True: 177 | # Constraints (Em<0.2 and Ea<10 deg): 178 | # if abs(normalized_torque_ripple)>=0.2 or abs(normalized_force_error_magnitude) >= 0.2 or abs(force_error_angle) > 10 or SafetyFactor < 1.5: 179 | # if abs(normalized_torque_ripple)>=0.2 or abs(normalized_force_error_magnitude) >= 0.2 or abs(force_error_angle) > 10 or FRW < 1: 180 | # if abs(normalized_torque_ripple)>=0.2 or abs(normalized_force_error_magnitude) >= 0.2 or abs(force_error_angle) > 10: 181 | if abs(normalized_torque_ripple)>=0.3 or abs(normalized_force_error_magnitude) >= 0.35 or abs(force_error_angle) > 20 or FRW < 0.5: 182 | print('[Problem_BlessSyn] Constraints are violated:') 183 | if abs(normalized_torque_ripple)>=0.3: 184 | print('\tabs(normalized_torque_ripple)>=0.3 | (=%f)' % (normalized_torque_ripple)) 185 | if abs(normalized_force_error_magnitude) >= 0.35: 186 | print('\tabs(normalized_force_error_magnitude) >= 0.35 | (=%f)' % (normalized_force_error_magnitude)) 187 | if abs(force_error_angle) > 20: 188 | print('\tabs(force_error_angle) > 20 | (=%f)' % (force_error_angle)) 189 | if FRW < 0.5: 190 | print('\tFRW < 0.5 | (=%f)' % (FRW)) 191 | # f1, f2, f3 = get_bad_fintess_values(machine_type='PMSM') 192 | # f1, f2, f3 = get_bad_fintess_values(machine_type='CPPM') 193 | f1, f2, f3 = get_bad_fintess_values(machine_type='CSPPM') 194 | print('[Problem_BearinglessSyn] f1,f2,f3:',f1,f2,f3) 195 | 196 | break 197 | 198 | ad.counter_fitness_return += 1 199 | print('[Problem_BlessSyn] Fitness: %d, %d\n----------------'%(ad.counter_fitness_called, ad.counter_fitness_return)) 200 | # raise KeyboardInterrupt 201 | return [f1, f2, f3] 202 | # return [f1, f2, torque_ripple] 203 | 204 | # Return number of objectives 205 | def get_nobj(self): 206 | return 3 207 | 208 | # Return bounds of decision variables (a.k.a. chromosome) 209 | def get_bounds(self): 210 | global ad 211 | print('[Problem_BlessSyn] Problem_BearinglessSynchronousDesign.get_bounds:', ad.acm_template.bounds_denorm) 212 | min_b, max_b = np.asarray(ad.acm_template.bounds_denorm).T 213 | return ( min_b.tolist(), max_b.tolist() ) 214 | 215 | # Return function name 216 | def get_name(self): 217 | return "Bearingless PMSM Design" 218 | 219 | import pygmo as pg 220 | # algorithm = pg.algorithm(pg.sade(gen=100)) 221 | # pop = pg.population(prob, size=50) 222 | # pop = algorithm.evolve(pop) 223 | # best_fitness = pop.champion_f 224 | # best_solution = pop.champion_x 225 | # print("Best Fitness:", best_fitness) 226 | # print("Best Solution:", best_solution) 227 | def get_prob(): 228 | udp = Problem_BearinglessSynchronousDesign() 229 | prob = pg.problem(udp) 230 | return udp, prob #, popsize 231 | -------------------------------------------------------------------------------- /codes3/PyX_classes.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | import pyx, abc 3 | import numpy as np 4 | 5 | ## 坐标系 6 | @dataclass 7 | class quadrature_coordinate(object): 8 | xlength: float = 10 9 | ylength: float = 10 10 | 11 | def draw(self, pu, xmirror=False, ymirror=False, settings=[]): 12 | pu.pyx_arrow( [ self.xlength, 0], settings=settings) 13 | if xmirror: pu.pyx_line([0,0], [-self.xlength, 0], settings=settings) 14 | pu.pyx_arrow( [0, self.ylength], settings=settings) 15 | if ymirror: pu.pyx_line([0,0], [0, -self.ylength], settings=settings) 16 | 17 | 18 | ## 绘制任意函数波形 19 | @dataclass 20 | class function_as_graph(object): 21 | def draw(self, pu, list_of_points, settings=[]): 22 | last_point = list_of_points[0] 23 | for point in list_of_points[1:]: 24 | pu.pyx_line(last_point, point, settings=settings) 25 | last_point = point 26 | 27 | global_size = 1.5 28 | global_label_bias_scale = 1.5 29 | 30 | ## 电路:电压源 31 | @dataclass 32 | class voltage_source(object): 33 | radius: float = global_size 34 | center: tuple = (0,0) 35 | bool_vertical: bool = False 36 | 37 | def __post_init__(self): 38 | center = self.center 39 | radius = self.radius 40 | self.anchors = [ 41 | (center[0]+radius, center[1]), 42 | (center[0]-radius, center[1]), 43 | (center[0], center[1]+radius), 44 | (center[0], center[1]-radius) 45 | ] 46 | 47 | def draw(self, pu, label=None): 48 | 49 | # 圆 50 | pu.pyx_circle(radius=self.radius, center=self.center) 51 | 52 | # 加号 53 | sign_bias = self.radius/2 54 | stroke_length = self.radius/4 55 | if self.bool_vertical: 56 | center_bias1 = [self.center[0], self.center[1]+sign_bias-stroke_length] 57 | center_bias2 = [self.center[0], self.center[1]+sign_bias+stroke_length] 58 | center_bias3 = [self.center[0]-stroke_length, self.center[1]+sign_bias] 59 | center_bias4 = [self.center[0]+stroke_length, self.center[1]+sign_bias] 60 | else: 61 | center_bias1 = [self.center[0], self.center[1]+sign_bias-stroke_length] 62 | center_bias2 = [self.center[0], self.center[1]+sign_bias+stroke_length] 63 | center_bias3 = [self.center[0]-stroke_length, self.center[1]+sign_bias] 64 | center_bias4 = [self.center[0]+stroke_length, self.center[1]+sign_bias] 65 | pu.pyx_line(center_bias1, center_bias2) 66 | pu.pyx_line(center_bias3, center_bias4) 67 | 68 | # 减号 69 | sign_bias = - sign_bias 70 | if self.bool_vertical: 71 | center_bias3 = [self.center[0]-stroke_length, self.center[1]+sign_bias] 72 | center_bias4 = [self.center[0]+stroke_length, self.center[1]+sign_bias] 73 | else: 74 | center_bias3 = [self.center[0]-stroke_length, self.center[1]+sign_bias] 75 | center_bias4 = [self.center[0]+stroke_length, self.center[1]+sign_bias] 76 | pu.pyx_line(center_bias3, center_bias4) 77 | 78 | if label is not None: 79 | self.add_label(pu, label) 80 | 81 | def add_label(self, pu, label): 82 | if self.bool_vertical: 83 | pu.pyx_text([0.5*(self.anchors[0][0]+self.anchors[1][0])-global_label_bias_scale*self.radius, 84 | 0.5*(self.anchors[0][1]+self.anchors[1][1])+global_label_bias_scale*self.radius], 85 | label, settings=['night-blue']) 86 | else: 87 | pu.pyx_text([0.5*(self.anchors[0][0]+self.anchors[1][0])-global_label_bias_scale*self.radius, 88 | 0.5*(self.anchors[0][1]+self.anchors[1][1])+global_label_bias_scale*self.radius], 89 | label, settings=['night-blue']) 90 | 91 | 92 | ## 电路:被动元件父类 93 | @dataclass 94 | class passive_components(object): 95 | size: float = global_size*2 96 | location: tuple = (0,0) 97 | bool_vertical: bool = False 98 | angle: float = 0.0 99 | 100 | def __post_init__(self): 101 | location = self.location 102 | size = self.size 103 | if self.bool_vertical: 104 | self.anchors = [ 105 | (location[0], location[1]), 106 | (location[0], location[1]+size) 107 | ] 108 | else: 109 | self.anchors = [ 110 | (location[0], location[1]), 111 | (location[0], location[1]+size) 112 | ] 113 | 114 | @abc.abstractmethod 115 | def draw(self, pu): 116 | pass 117 | 118 | def add_label(self, pu, label): 119 | if not self.bool_vertical: 120 | pu.pyx_text([0.5*(self.anchors[0][0]+self.anchors[1][0]), 121 | 0.5*(self.anchors[0][1]+self.anchors[1][1])+global_label_bias_scale*self.size], 122 | label, settings=['night-blue']) 123 | else: 124 | pu.pyx_text([0.5*(self.anchors[0][0]+self.anchors[1][0]-global_label_bias_scale*self.size), 125 | 0.5*(self.anchors[0][1]+self.anchors[1][1])], 126 | label, settings=['night-blue']) 127 | 128 | ## 电路:电感 129 | @dataclass 130 | class inductance(passive_components): 131 | def draw(self, pu, label=None): 132 | graph = function_as_graph() 133 | def f1(t): 134 | return np.abs(np.sin(2*np.pi* ( (0.5)*t) )) 135 | if self.bool_vertical: 136 | graph.draw( pu, [ (-f1(t)+self.location[0], 137 | t+self.location[1]) \ 138 | for t in np.arange(0, self.size+.05, 0.05)] ) 139 | else: 140 | graph.draw( pu, [ (t +self.location[0], 141 | f1(t)+self.location[1]) \ 142 | for t in np.arange(0, self.size+.05, 0.05)] ) 143 | 144 | if label is not None: 145 | self.add_label(pu, label) 146 | 147 | ## 电路:电感 148 | @dataclass 149 | class resistance(passive_components): 150 | def draw(self, pu, label=None): 151 | if self.bool_vertical: 152 | pu.pyx_line([self.location[0], self.location[1]], 153 | [self.location[0]-self.size/4, self.location[1]+self.size*1/8],) 154 | pu.pyx_line([self.location[0]-self.size/4, self.location[1]+self.size*1/8], 155 | [self.location[0], self.location[1]+self.size*2/8],) 156 | 157 | pu.pyx_line([self.location[0], self.location[1]+self.size*2/8], 158 | [self.location[0]-self.size/4, self.location[1]+self.size*3/8]) 159 | pu.pyx_line([self.location[0]-self.size/4, self.location[1]+self.size*3/8], 160 | [self.location[0], self.location[1]+self.size*4/8],) 161 | 162 | pu.pyx_line([self.location[0], self.location[1]+self.size*4/8], 163 | [self.location[0]-self.size/4, self.location[1]+self.size*5/8]) 164 | pu.pyx_line([self.location[0]-self.size/4, self.location[1]+self.size*5/8], 165 | [self.location[0], self.location[1]+self.size*6/8],) 166 | 167 | pu.pyx_line([self.location[0], self.location[1]+self.size*6/8], 168 | [self.location[0]-self.size/4, self.location[1]+self.size*7/8]) 169 | pu.pyx_line([self.location[0]-self.size/4, self.location[1]+self.size*7/8], 170 | [self.location[0], self.location[1]+self.size*8/8],) 171 | 172 | if label is not None: 173 | self.add_label(pu, label) 174 | 175 | ## 电路:简单连接 176 | def distance(p1, p2): 177 | return np.sqrt((p2[0]-p1[0])**2 + (p2[1]-p1[1])**2) 178 | def easy_connect(pu, obj1, obj2): 179 | dict_of_distances = {(p1,p2): distance(p1,p2) for p1 in obj1.anchors for p2 in obj2.anchors} 180 | 181 | # debug 182 | # for k,v in dict_of_distances.items(): 183 | # print(k, ':', v) 184 | 185 | # 找到距离最小的一对点 186 | p1, p2 = min(dict_of_distances, key=dict_of_distances.get) 187 | pu.pyx_line(p1, p2) 188 | 189 | ## 电路:连接点 190 | @dataclass 191 | class connection_point(object): 192 | location: tuple = (0,0) 193 | size: float = 0.15 194 | 195 | def __post_init__(self): 196 | location = self.location 197 | self.anchors = [ 198 | (location[0], location[1]), 199 | ] 200 | def draw(self, pu): 201 | pu.pyx_marker(self.location, size=self.size) 202 | -------------------------------------------------------------------------------- /codes3/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horychen/ACMOP/1ec13664f6696af2c2470679fe3a17340735df33/codes3/__init__.py -------------------------------------------------------------------------------- /codes3/angle_error_nick.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | def angle_error(alpha_star, alpha_actual): 3 | """ 4 | This function computes the angle error between a desired and actual angle. 5 | 6 | Input Variables: 7 | 8 | alpha_star = an array like object containing all of the desired vector 9 | angles in degrees 10 | 11 | alpha_actual = an array like object containing all of the actual vector 12 | angles in degrees 13 | 14 | Note: alpha_star and alpha_actual must be the same dimension 15 | 16 | Output Variables: 17 | 18 | angle_err: the angle error in degrees 19 | 20 | 21 | Example: 22 | 23 | alpha_star = [5, 60, 359] 24 | alpha_actual = [358, 65, 6] 25 | 26 | err = angle_error(alpha_star, alpha_actual) 27 | 28 | print(err) # --> [-7, 5, 7] 29 | 30 | """ 31 | 32 | #ensure that input variables are numpy arrays 33 | alpha_star = np.atleast_1d(alpha_star) 34 | alpha_actual = np.atleast_1d(alpha_actual) 35 | 36 | N = alpha_star.shape[0] #determine number of input angles 37 | 38 | #first preallocate arrays for unit vectors directed at every angle 39 | vectors_star = np.zeros((N,2)) 40 | vectors = np.zeros((N,2)) 41 | 42 | #unit vectors for desired angle 43 | vectors_star[:,0] = np.cos( np.deg2rad(alpha_star) ) 44 | vectors_star[:,1] = np.sin( np.deg2rad(alpha_star) ) 45 | 46 | #unit vectors for actual angle 47 | vectors[:,0] = np.cos( np.deg2rad(alpha_actual) ) 48 | vectors[:,1] = np.sin( np.deg2rad(alpha_actual) ) 49 | 50 | #determine angle between vectors in degrees (note that this is only the angle magnitude): 51 | #This is just doing the dot product between all corresponding unit vectors 52 | error_angle_mag = np.rad2deg( np.arccos( (vectors_star*vectors).sum(axis = 1) ) ) 53 | 54 | #to determine the error angle direction we can use the cross product between the vectors 55 | #positive cross product means the desired vector lags the actual vector and the error angle 56 | #is positive 57 | sign = np.sign( np.cross(vectors_star, vectors) ) 58 | 59 | angle_err = sign*error_angle_mag 60 | 61 | return angle_err 62 | 63 | def compute_angle_error(alpha_star, alpha_actual): 64 | 65 | #both angle arrays are assumed to be in degrees 66 | N = alpha_star.shape[0] #determine number of input angles 67 | 68 | #first preallocate arrays for unit vectors directed at every angle 69 | vectors_star = np.zeros((N,2)) 70 | vectors = np.zeros((N,2)) 71 | 72 | #unit vectors for desired angle 73 | vectors_star[:,0] = np.cos( np.deg2rad(alpha_star) ) 74 | vectors_star[:,1] = np.sin( np.deg2rad(alpha_star) ) 75 | #unit vectors for actual angle 76 | vectors[:,0] = np.cos( np.deg2rad(alpha_actual) ) 77 | vectors[:,1] = np.sin( np.deg2rad(alpha_actual) ) 78 | 79 | #determine angle between vectors in degrees (note that this is only the angle magnitude): 80 | #This is just doing the cross product between all corresponding unit vectors 81 | error_angle_mag = np.rad2deg( np.arccos((vectors_star*vectors).sum(axis = 1)) ) 82 | 83 | #to determine the error angle direction we can use the cross product between the vectors 84 | #positive cross product means the desired vector lags the actual vector and the error angle 85 | #is positive 86 | sign = np.sign( np.cross(vectors_star, vectors) ) 87 | 88 | return sign*error_angle_mag 89 | 90 | def main(): 91 | 92 | alpha_star = [5, 60, 359, 150] 93 | alpha_actual = [358, 65, 6] 94 | 95 | err = angle_error(alpha_star, alpha_actual) 96 | print(err) # -> [-7, 5, 7] 97 | 98 | err = compute_angle_error(np.array(alpha_star), np.array(alpha_actual)) 99 | print(err) # -> [-7, 5, 7] 100 | 101 | if __name__ == '__main__': 102 | 103 | main() 104 | -------------------------------------------------------------------------------- /codes3/bearingless_induction_design.py: -------------------------------------------------------------------------------- 1 | import inner_rotor_motor, pyrhonen_procedure_as_function 2 | import logging 3 | from collections import OrderedDict 4 | from utility import acmop_parameter 5 | from pylab import np 6 | from pprint import pprint 7 | 8 | import CrossSectInnerNotchedRotor 9 | import CrossSectStator 10 | import Location2D 11 | 12 | class bearingless_induction_template(inner_rotor_motor.template_machine_as_numbers): 13 | ''' This is bearingless induction motor template 14 | ''' 15 | def __init__(self, fea_config_dict, spec_input_dict): 16 | # 初始化父类 17 | super(bearingless_induction_template, self).__init__(fea_config_dict, spec_input_dict) 18 | 19 | # 基本信息 20 | self.machine_type = 'BLIM' 21 | self.name = '__BLIM' 22 | 23 | # 初始化搜索空间 24 | GP = self.d['GP'] 25 | EX = self.d['EX'] 26 | SI = self.SI 27 | childGP = OrderedDict({ 28 | # IM Peculiar 29 | "mm_d_ro" : acmop_parameter("free", "rotor_slot_open_depth", None, [None, None], lambda GP,SI:None), 30 | "mm_w_rt" : acmop_parameter("free", "rotor_tooth_width", None, [None, None], lambda GP,SI:None), 31 | "deg_alpha_rt" : acmop_parameter("free", "rotor_tooth_span_angle", None, [None, None], lambda GP,SI:None), 32 | }) 33 | GP.update(childGP) 34 | 35 | if True: 36 | # Get Analytical Design 37 | 38 | # [2.1] Attain spec_derive_dict 39 | self.spec = spec = pyrhonen_procedure_as_function.desgin_specification(**spec_input_dict) 40 | print(spec.build_name()) 41 | spec.bool_bad_specifications = spec.pyrhonen_procedure() 42 | 43 | # Update GP 44 | if True: 45 | sgd = spec.spec_geometry_dict 46 | stator_outer_diameter_Dse = sgd['Radius_OuterStatorYoke'] * 2 47 | stator_inner_diameter_Dis = sgd['Radius_InnerStatorYoke'] * 2 48 | stator_yoke_height_h_ys = sgd['Radius_OuterStatorYoke'] - sgd['Radius_InnerStatorYoke'] 49 | stator_tooth_width_b_ds = sgd['Width_StatorTeethBody'] 50 | stator_tooth_height_h_ds = sgd['Width_StatorTeethNeck'] 51 | stator_slot_height_h_ss = stator_tooth_height_h_ds 52 | stator_inner_radius_r_is = 0.5*stator_inner_diameter_Dis 53 | rotor_outer_radius_r_or = sgd['Radius_OuterRotor'] 54 | rotor_outer_diameter_Dr = 2.0*rotor_outer_radius_r_or 55 | split_ratio = stator_inner_diameter_Dis / stator_outer_diameter_Dse 56 | 57 | EX['stator_slot_area'] = stator_slot_area = np.pi/(4*SI['Qs']) * ((stator_outer_diameter_Dse - 2*stator_yoke_height_h_ys)**2 - stator_inner_diameter_Dis**2) - stator_tooth_width_b_ds * stator_tooth_height_h_ds 58 | 59 | slot_pitch_pps = np.pi * (stator_inner_diameter_Dis + stator_slot_height_h_ss) / SI['Qs'] 60 | kov = 1.8 # \in [1.6, 2.0] 61 | EX['end_winding_length_Lew'] = end_winding_length_Lew = np.pi*0.5 * (slot_pitch_pps + stator_tooth_width_b_ds) + slot_pitch_pps*kov * (SI['coil_pitch_y'] - 1) 62 | 63 | Q = SI['Qs'] 64 | p = SI['p'] 65 | # STATOR 66 | GP['deg_alpha_st'].value = 360/Q - 2 # deg 67 | GP['deg_alpha_sto'].value = GP['deg_alpha_st'].value/2 # im_template uses alpha_so as 0. 68 | GP['mm_r_si'].value = 1e3*stator_inner_radius_r_is # mm 69 | GP['mm_r_so'].value = 1e3*stator_outer_diameter_Dse/2 # mm 70 | GP['mm_d_sto'].value = 1 # mm 71 | GP['mm_d_stt'].value = 1.5*GP['mm_d_sto'].value 72 | GP['mm_d_st'].value = 1e3*(0.5*stator_outer_diameter_Dse - stator_yoke_height_h_ys) - GP['mm_r_si'].value - GP['mm_d_stt'].value # mm 73 | GP['mm_d_sy'].value = 1e3*stator_yoke_height_h_ys # mm 74 | GP['mm_w_st'].value = 1e3*stator_tooth_width_b_ds # mm 75 | # ROTOR 76 | GP['mm_d_sleeve'].value = 0.0 77 | GP['mm_d_mech_air_gap'].value = sgd['Length_AirGap'] 78 | GP['split_ratio'].value = split_ratio 79 | GP['mm_r_ro'].value = 1e3*rotor_outer_radius_r_or 80 | # GP['mm_r_ri'].value = sgd['Radius_Shaft'] 81 | # BLIM specific 82 | GP['mm_d_ro'].value = sgd['Length_HeadNeckRotorSlot'] 83 | GP['mm_w_rt'].value = sgd['rotor_tooth_width_b_dr'] 84 | GP['deg_alpha_rt'].value = 360/Q - sgd['Width_RotorSlotOpen']/(2*np.pi*rotor_outer_radius_r_or)*360 85 | 86 | GP = None 87 | print('!!![bearingless_induction_motor_design.py] GP is set to None for now.') 88 | print('!!![bearingless_induction_motor_design.py] DEBUG pending: GP has different order from x_denorm parsed in population.bearingless_induction_motor_design.local_design_variant().') 89 | 90 | import population 91 | # load initial design using the obsolete class bearingless_induction_motor_design 92 | self.obsolete_template = population.bearingless_induction_motor_design(spec_input_dict, spec.spec_derive_dict, spec.spec_geometry_dict, fea_config_dict) 93 | # self.spec_geometry_dict = spec.spec_geometry_dict 94 | 95 | 96 | # 定义搜索空间,determine bounds 97 | self.bounds_denorm = spec.get_im_classic_bounds(which_filter=fea_config_dict['which_filter']) 98 | bound_filter = spec.bound_filter 99 | otnb = spec.original_template_neighbor_bounds 100 | 101 | # Template's Other Properties (Shared by the swarm) 102 | self.obsolete_template 103 | 104 | def get_template_neighbor_bounds(self, GP, SI): 105 | ''' The bounds are determined around the template design. 106 | ''' 107 | 108 | Q = self.SI['Qs'] 109 | p = self.SI['p'] 110 | s = self.SI['no_segmented_magnets'] 111 | 112 | GP = self.d['GP'] 113 | 114 | original_template_neighbor_bounds = { 115 | # STATOR 116 | "deg_alpha_st": [ 0.35*360/Q, 0.9*360/Q], 117 | "mm_d_sto": [ 0.5, 5], 118 | "mm_d_st": [0.8*GP['mm_d_st'].value, 1.2*GP['mm_d_st'].value], 119 | "mm_r_so": [1.0*GP['mm_r_so'].value, 1.2*GP['mm_r_so'].value], 120 | "mm_w_st": [0.8*GP['mm_w_st'].value, 1.2*GP['mm_w_st'].value], 121 | # ROTOR 122 | "mm_d_sleeve": [3, 6], 123 | "split_ratio": [0.4, 0.6], # Binder-2020-MLMS-0953@Fig.7 124 | "mm_d_pm": [2.5, 7], 125 | "mm_d_ri": [0.8*GP['mm_d_ri'].value, 1.2*GP['mm_d_ri'].value], 126 | # SPMSM specific 127 | "deg_alpha_rm": [0.6*360/(2*p), 1.0*360/(2*p)], 128 | "mm_d_rp": [2.5, 6], 129 | "deg_alpha_rs": [0.8*360/(2*p)/s, 0.975*360/(2*p)/s], 130 | "mm_d_rs": [2.5, 6] 131 | } 132 | return original_template_neighbor_bounds 133 | 134 | def build_design_parameters_list(self): 135 | GP = self.d['GP'] 136 | SI = self.SI 137 | # obsolete feature 138 | design_parameters = [ 139 | 0.0, 140 | 0.0, 141 | 0.0, 142 | 0.0, 143 | 0.0, 144 | 0.0, 145 | 0.0, 146 | 0.0, 147 | 0.0, #'mm_r_st', 148 | 0.0, #'mm_r_sf', 149 | 0.0, #'mm_r_sb', 150 | 0.0, 151 | 0.0, 152 | 0.0, 153 | 0.0, 154 | 0.0, 155 | 0.0, 156 | 0.0, 157 | 0.0, 158 | 0.0, 159 | 0.0, 160 | 0.0, 161 | 0.0 162 | ] 163 | return design_parameters 164 | 165 | def build_x_denorm(self): 166 | ''' 覆盖父类的同名方法 167 | ''' 168 | return self.obsolete_template.build_x_denorm() 169 | 170 | class bearingless_spmsm_design_variant(inner_rotor_motor.variant_machine_as_objects): 171 | 172 | def __init__(self, template=None, x_denorm=None, counter=None, counter_loop=None): 173 | if x_denorm is None: 174 | raise 175 | 176 | # 初始化父类 177 | super(bearingless_spmsm_design_variant, self).__init__(template, x_denorm, counter, counter_loop) 178 | 179 | # 检查几何变量之间是否有冲突 180 | GP = self.template.d['GP'] 181 | SI = self.template.SI 182 | self.check_invalid_design(GP, SI) 183 | 184 | im_variant = population.bearingless_induction_motor_design.local_design_variant(template, 0, counter, x_denorm) 185 | 186 | 187 | # # Parts 188 | # self.rotorCore = CrossSectInnerNotchedRotor.CrossSectInnerNotchedRotor( 189 | # name = 'NotchedRotor', 190 | # mm_d_pm = GP['mm_d_pm'].value, 191 | # deg_alpha_rm = GP['deg_alpha_rm'].value, # angular span of the pole: class type DimAngular 192 | # deg_alpha_rs = GP['deg_alpha_rs'].value, # segment span: class type DimAngular 193 | # mm_d_ri = GP['mm_d_ri'].value, # rotor iron thickness: class type DimLinear 194 | # mm_r_ri = GP['mm_r_ri'].value, # inner radius of rotor: class type DimLinear 195 | # mm_d_rp = GP['mm_d_rp'].value, # interpolar iron thickness: class type DimLinear 196 | # mm_d_rs = GP['mm_d_rs'].value, # inter segment iron thickness: class type DimLinear 197 | # p = template.SI['p'], # Set pole-pairs to 2 198 | # s = template.SI['no_segmented_magnets'], # Set magnet segments/pole to 4 199 | # location = Location2D.Location2D(anchor_xy=[0,0], deg_theta=0)) 200 | 201 | # self.shaft = CrossSectInnerNotchedRotor.CrossSectShaft(name = 'Shaft', 202 | # notched_rotor = self.rotorCore 203 | # ) 204 | 205 | # self.rotorMagnet = CrossSectInnerNotchedRotor.CrossSectInnerNotchedMagnet( name = 'RotorMagnet', 206 | # notched_rotor = self.rotorCore 207 | # ) 208 | 209 | # self.stator_core = CrossSectStator.CrossSectInnerRotorStator( name = 'StatorCore', 210 | # deg_alpha_st = GP['deg_alpha_st'].value, #40, 211 | # deg_alpha_sto = GP['deg_alpha_sto'].value, #20, 212 | # mm_r_si = GP['mm_r_si'].value, 213 | # mm_d_sto = GP['mm_d_sto'].value, 214 | # mm_d_stt = GP['mm_d_stt'].value, 215 | # mm_d_st = GP['mm_d_st'].value, 216 | # mm_d_sy = GP['mm_d_sy'].value, 217 | # mm_w_st = GP['mm_w_st'].value, 218 | # mm_r_st = 0.0, # =0 219 | # mm_r_sf = 0.0, # =0 220 | # mm_r_sb = 0.0, # =0 221 | # Q = template.SI['Qs'], 222 | # location = Location2D.Location2D(anchor_xy=[0,0], deg_theta=0) 223 | # ) 224 | 225 | # self.coils = CrossSectStator.CrossSectInnerRotorStatorWinding(name = 'Coils', 226 | # stator_core = self.stator_core) 227 | 228 | # self.sleeve = CrossSectInnerNotchedRotor.CrossSectSleeve( 229 | # name = 'Sleeve', 230 | # notched_magnet = self.rotorMagnet, 231 | # d_sleeve = GP['mm_d_sleeve'].value 232 | # ) 233 | 234 | # #03 Mechanical Parameters 235 | # self.update_mechanical_parameters() 236 | 237 | def check_invalid_design(self, GP, SI): 238 | pass 239 | 240 | -------------------------------------------------------------------------------- /codes3/initialDesignFSPM.py: -------------------------------------------------------------------------------- 1 | #%% 基本性能计算:电磁负荷、转子外径 2 | from pylab import np, plt 3 | from dataclasses import dataclass 4 | 5 | @dataclass 6 | class UserInput_FSPM(): 7 | mec_power: float 8 | speed_rpm: float 9 | guess_air_gap_flux_density_B: float = 0.8 10 | guess_stator_yoke_flux_density_Bsy: float = 0.3 11 | mm_stack_length: float = 10 12 | Pa_TangentialStress: float = 1000 # = 0.5*A*B 13 | m: int = 3 14 | Qs: int = 24 # 12 15 | pm: int = 22 # 10 16 | 17 | def __post_init__(self): 18 | self.Zs = self.Qs * 2 19 | # self.required_torque = self.mec_power/(2*np.pi*self.speed_rpm/60) 20 | # self.guess_air_gap_flux_density_B = 1.2 21 | # guess_linear_current_density_A = self.Pa_TangentialStress*2/self.guess_air_gap_flux_density_B 22 | # # (6.2) 23 | # self.RotorActiveVolumn_Vr = self.required_torque / (2*self.Pa_TangentialStress) 24 | # self.RotorActiveCrossSectArea_Sr = self.RotorActiveVolumn_Vr / (self.mm_stack_length*1e-3) 25 | # self.RotorOuterRadius_r_or = np.sqrt(self.RotorActiveCrossSectArea_Sr/np.pi) 26 | # self.mm_r_ro = self.RotorOuterRadius_r_or*1e3 27 | 28 | 29 | number_of_cells_per_phase_Nc = self.Qs/self.m 30 | stator_angular_slot_span_theta_s = 2*np.pi / self.Qs 31 | 'The angular difference between stator and rotor teeth should be multiple of 2*pi_elec / (2*self.m)' 32 | print('The original work suggests:') 33 | for i in range(1, 2*self.m): 34 | rotor_angular_slot_span_theta_r_verPositive = stator_angular_slot_span_theta_s/(1+i/2/self.m) 35 | rotor_angular_slot_span_theta_r_verNegative = stator_angular_slot_span_theta_s/(1-i/2/self.m) 36 | print( 'pm should be:', 37 | 2*np.pi/rotor_angular_slot_span_theta_r_verPositive, \ 38 | 2*np.pi/rotor_angular_slot_span_theta_r_verNegative, \ 39 | number_of_cells_per_phase_Nc*(self.m+0.5*i),\ 40 | number_of_cells_per_phase_Nc*(self.m-0.5*i) ) 41 | # pm = 2*np.pi/rotor_angular_slot_span_theta_r_verNegative 42 | # pm = (self.Qs + self.Qs*i/2/self.m) 43 | # pm = (self.Qs + number_of_cells_per_phase_Nc*i/2) 44 | # pm = number_of_cells_per_phase_Nc * (self.m + i*0.5) 45 | 46 | print('New clean version with a different factor of i*0.25 instead of i*0.5:') 47 | print([number_of_cells_per_phase_Nc*(self.m+0.25*i) for i in range(1, 2*self.m)]) 48 | print([number_of_cells_per_phase_Nc*(self.m-0.25*i) for i in range(1, 2*self.m)]) 49 | print('And pa should be:') 50 | print([number_of_cells_per_phase_Nc*(self.m+0.25*i) - self.Qs/2 for i in range(1, 2*self.m)]) 51 | print([number_of_cells_per_phase_Nc*(self.m-0.25*i) - self.Qs/2 for i in range(1, 2*self.m)]) 52 | 53 | # slice = UserInput_FSPM(mec_power=100, speed_rpm=200, Pa_TangentialStress=12000) 54 | # slice = UserInput_FSPM(mec_power=50, speed_rpm=400, Pa_TangentialStress=3000) 55 | slice = UserInput_FSPM(mec_power=30, speed_rpm=400, Pa_TangentialStress=3000) 56 | required_torque = slice.mec_power/(2*np.pi*slice.speed_rpm/60) 57 | guess_linear_current_density_A = slice.Pa_TangentialStress*2/slice.guess_air_gap_flux_density_B 58 | 59 | # (6.2) 60 | RotorActiveVolumn_Vr = required_torque / (2*slice.Pa_TangentialStress) 61 | RotorActiveCrossSectArea_Sr = RotorActiveVolumn_Vr / (slice.mm_stack_length*1e-3) 62 | RotorOuterRadius_r_or = np.sqrt(RotorActiveCrossSectArea_Sr/np.pi) 63 | mm_r_ro = RotorOuterRadius_r_or*1e3 64 | 65 | aspect_ratio__rotor_axial_to_diameter_ratio = 2*mm_r_ro/slice.mm_stack_length 66 | 67 | 68 | #% 磁路法计算:永磁体大小? 69 | 70 | mm_air_gap_length = 3 71 | mm_r_si = mm_r_ro + mm_air_gap_length 72 | mm_r_airgap = mm_r_ro + mm_air_gap_length*0.5 73 | 74 | rotor_to_stator_tooth_width_ratio = 1.4 75 | fea_config_dict = { "WindingFill": 0.3, 76 | "Js": 4e6, # A/m^2 77 | } 78 | 79 | mm_stator_tooth_width_w_UCoreWidth = 2*np.pi*mm_r_si / slice.Qs / 4 80 | mm_rotor_tooth_width_w_rt = rotor_to_stator_tooth_width_ratio * mm_stator_tooth_width_w_UCoreWidth # Empirical: slightly wider (5.18) Habil-Gruber 81 | 82 | mmf_per_phase = guess_linear_current_density_A * 2*np.pi*mm_r_airgap / slice.m 83 | mmf_per_slot = guess_linear_current_density_A * 2*np.pi*mm_r_airgap / slice.Qs 84 | # stator_current_per_phase = mmf_per_phase / 2 * N * I 85 | 86 | stator_slot_area = mmf_per_slot / fea_config_dict['Js'] / fea_config_dict['WindingFill'] 87 | 88 | # stator slot depth 89 | stator_inner_radius_r_is_eff = mm_r_si 90 | temp = (2*np.pi*stator_inner_radius_r_is_eff - slice.Qs*mm_stator_tooth_width_w_UCoreWidth*3) # mm_stator_tooth_width_w_UCoreWidth * 3 = stator non-slot part width 91 | stator_tooth_depth_d_st = ( np.sqrt(temp**2 + 4*np.pi*stator_slot_area*slice.Qs) - temp ) / (2*np.pi) 92 | print(stator_tooth_depth_d_st) 93 | 94 | mm_d_sy = slice.guess_air_gap_flux_density_B * mm_stator_tooth_width_w_UCoreWidth / slice.guess_stator_yoke_flux_density_Bsy 95 | 96 | 97 | # mm_r_so = mm_r_si + mm_d_st + mm_d_sy 98 | # split_ratio = mm_r_ro / mm_r_so 99 | 100 | # %% 101 | 102 | from pylab import np 103 | 104 | Qs = 24 105 | g = 3 106 | r_ro = 80 107 | w_PM = 3 108 | d_stt = 6 109 | 110 | k1, k2, k3, k4 = 0.7, 1.2, 1.2, 1.0 111 | 112 | r_si = r_ro + g 113 | r_so = k1 / r_ro 114 | print(f'w_PM <= {2*np.pi*r_si/4/Qs}') 115 | if w_PM > 2*np.pi*r_si/4/Qs: 116 | raise 117 | w_st = 2*(k2*w_PM) + w_PM 118 | d_sy = k3*(k2*w_PM) 119 | w_rt = k4*(k2*w_PM) 120 | 121 | d_st = r_so - r_si - d_sy - d_stt 122 | 123 | # %% 124 | -------------------------------------------------------------------------------- /codes3/material.py: -------------------------------------------------------------------------------- 1 | #coding:u8 2 | from pylab import * 3 | mu0 = (4*pi*1e-7) 4 | 5 | # fname = "../Arnon5/Arnon-5-NGOES-Magnetization-Curve_Redo.csv" 6 | # data = loadtxt(fname, delimiter=',', skiprows=1) # https://docs.scipy.org/doc/numpy-1.13.0/reference/generated/numpy.loadtxt.html 7 | # x = data[:,0] * 1000/(4*np.pi) # Oesteds to A/m 8 | # y = data[:,1] * 1e-4 # Gauss to Tesla 9 | 10 | # fname = "../Arnon5/M-19-Steel-BH-Curve.txt" 11 | # y, x = loadtxt(fname, unpack=True, usecols=(0,1)) 12 | 13 | fname = "../Arnon5/M-19-Steel-BH-Curve-afterJMAGsmooth.txt" 14 | y, x = loadtxt(fname, unpack=True, usecols=(1,0)) 15 | y = y[1:] # skip the (0,0) point will be essential for pade approximate to work 16 | x = x[1:] 17 | print(fname) 18 | 19 | def rational(x, p, q): # pade appximate 20 | """ https://stackoverflow.com/questions/29815094/rational-function-curve-fitting-in-python 21 | The general rational function description. 22 | p is a list with the polynomial coefficients in the numerator 23 | q is a list with the polynomial coefficients (except the first one) 24 | in the denominator 25 | The zeroth order coefficient of the denominator polynomial is fixed at 1. 26 | Numpy stores coefficients in [x**2 + x + 1] order, so the fixed 27 | zeroth order denominator coefficent must comes last. (Edited.) 28 | """ 29 | return np.polyval(p, x) / np.polyval(q + [1.0], x) 30 | 31 | def rational8_8(x, p0, p1, p2, p3, p4, p5, p6, p7, q1, q2, q3, q4, q5, q6, q7): 32 | '''88''' 33 | return rational(x, [p0, p1, p2, p3, p4, p5, p6, p7], [q1, q2, q3, q4, q5, q6, q7]) 34 | def rational8_7(x, p0, p1, p2, p3, p4, p5, p6, p7, q1, q2, q3, q4, q5, q6): 35 | '''87''' 36 | return rational(x, [p0, p1, p2, p3, p4, p5, p6, p7], [q1, q2, q3, q4, q5, q6]) 37 | def rational7_7(x, p0, p1, p2, p3, p4, p5, p6, q1, q2, q3, q4, q5, q6): 38 | '''77''' 39 | return rational(x, [p0, p1, p2, p3, p4, p5, p6], [q1, q2, q3, q4, q5, q6]) 40 | def rational7_6(x, p0, p1, p2, p3, p4, p5, p6, q1, q2, q3, q4, q5): 41 | '''76''' 42 | return rational(x, [p0, p1, p2, p3, p4, p5, p6], [q1, q2, q3, q4, q5]) 43 | def rational7_5(x, p0, p1, p2, p3, p4, p5, p6, q1, q2, q3, q4): 44 | '''75''' 45 | return rational(x, [p0, p1, p2, p3, p4, p5, p6], [q1, q2, q3, q4]) 46 | def rational6_6(x, p0, p1, p2, p3, p4, p5, q1, q2, q3, q4, q5): 47 | '''66''' 48 | return rational(x, [p0, p1, p2, p3, p4, p5], [q1, q2, q3, q4, q5]) 49 | def rational6_5(x, p0, p1, p2, p3, p4, p5, q1, q2, q3, q4): 50 | '''65''' 51 | return rational(x, [p0, p1, p2, p3, p4, p5], [q1, q2, q3, q4]) 52 | def rational6_4(x, p0, p1, p2, p3, p4, p5, q1, q2, q3): 53 | '''64''' 54 | return rational(x, [p0, p1, p2, p3, p4, p5], [q1, q2, q3]) 55 | def rational5_5(x, p0, p1, p2, p3, p4, q1, q2, q3, q4): 56 | '''55''' 57 | return rational(x, [p0, p1, p2, p3, p4], [q1, q2, q3, q4]) 58 | def rational5_3(x, p0, p1, p2, p3, p4, q1, q2): 59 | '''53''' 60 | return rational(x, [p0, p1, p2, p3, p4], [q1, q2]) 61 | def rational4_4(x, p0, p1, p2, p3, q1, q2, q3): 62 | '''44''' 63 | return rational(x, [p0, p1, p2, p3], [q1, q2, q3]) 64 | def rational4_3(x, p0, p1, p2, p3, q1, q2): 65 | '''43''' 66 | return rational(x, [p0, p1, p2, p3], [q1, q2]) 67 | def rational3_3(x, p0, p1, p2, q1, q2): 68 | '''33''' 69 | return rational(x, [p0, p1, p2], [q1, q2]) 70 | # x = np.linspace(0, 10, 100) 71 | # y = rational(x, [-0.2, 0.3, 0.5], [-1.0, 2.0]) 72 | # ynoise = y * (1.0 + np.random.normal(scale=0.1, size=x.shape)) 73 | 74 | ax1 = figure().gca() 75 | ax2 = figure().gca() 76 | 77 | from scipy.optimize import curve_fit 78 | global y_fit 79 | def fitting_test(ax1, ax2, x, y, rational_func, label): 80 | global y_fit 81 | 82 | popt, pcov = curve_fit(rational_func, x, y) #p0=(0.2, 0.3, 0.5, 1, -1.0, 2.0, 1), diag=(1./x.mean(),1./y.mean())) 83 | print('\n\n\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~') 84 | print(label) 85 | print(popt) 86 | y_fit = rational_func(x, *popt) 87 | 88 | ax1.plot(x, y_fit, label='fit'+label, alpha=0.33) 89 | ax2.plot(x, (y_fit - y) / y, label=label+'normalized fit error', alpha=0.33) 90 | 91 | print('accumulated error', sum((y_fit - y)/y)) 92 | 93 | return y_fit 94 | 95 | ''' BH curve and its pade apprximate [no (0,0) allowed] 96 | ''' 97 | # fitting_test(ax1, ax2, x, y, rational8_7, rational8_7.__doc__) 98 | # fitting_test(ax1, ax2, x, y, rational7_7, rational7_7.__doc__) 99 | fitting_test(ax1, ax2, x, y, rational7_6, rational7_6.__doc__) # after visual inspection, this is the best 100 | # fitting_test(ax1, ax2, x, y, rational6_6, rational6_6.__doc__) 101 | # fitting_test(ax1, ax2, x, y, rational6_5, rational6_5.__doc__) 102 | # fitting_test(ax1, ax2, x, y, rational6_4, rational6_4.__doc__) 103 | # fitting_test(ax1, ax2, x, y, rational5_5, rational5_5.__doc__) 104 | # fitting_test(ax1, ax2, x, y, rational5_3, rational5_3.__doc__) 105 | # fitting_test(ax1, ax2, x, y, rational4_4, rational4_4.__doc__) 106 | # fitting_test(ax1, ax2, x, y, rational4_3, rational4_3.__doc__) 107 | # fitting_test(ax1, ax2, x, y, rational3_3, rational3_3.__doc__) 108 | # fitting_test(ax1, ax2, x, y, rational8_8, rational8_8.__doc__) 109 | 110 | ax1.plot(x, y, '*', label='ori', alpha=0.33) 111 | ax1.grid() 112 | ax1.legend() 113 | ax2.grid() 114 | ax2.legend() 115 | 116 | ''' Dynamic Gradient/mu0 to H 117 | ''' 118 | ax3 = figure().gca() 119 | plot(x, gradient(y) / gradient(x) / mu0, '*', label='ori', alpha=0.33) 120 | plot(x, gradient(y_fit) / gradient(x) / mu0, 'o-', label='fit', alpha=0.33) 121 | ax3.grid() 122 | ax3.legend() 123 | 124 | ''' mu-H curve 125 | ''' 126 | ax4 = figure().gca() 127 | plot(x, y/x, '*', label='ori') 128 | plot(x, y_fit/x, '^', label='fit') 129 | 130 | ax4.grid() 131 | ax4.legend() 132 | 133 | ''' B-mu_r curve 134 | ''' 135 | ax5 = figure().gca() 136 | plot(y, y/x / mu0, '*', label='ori') 137 | plot(y_fit, y_fit/x / mu0, '^', label='fit') 138 | 139 | ax5.grid() 140 | ax5.legend() 141 | 142 | ''' Staiti Gradient (What you see in JMAG) 143 | ''' 144 | ax6 = figure().gca() 145 | plot(x, gradient(y) / gradient(x), '*', label='ori', alpha=0.33) 146 | plot(x, gradient(y_fit) / gradient(x), 'o-', label='fit', alpha=0.33) 147 | ax6.grid() 148 | ax6.legend() 149 | 150 | ''' Dynamic Gradient/mu0 to B 151 | ''' 152 | ax7 = figure().gca() 153 | plot( y, gradient(y) / gradient(x) / mu0, '*', label='ori', alpha=0.33) 154 | plot(y_fit, gradient(y_fit) / gradient(x) / mu0, 'o-', label='fit', alpha=0.33) 155 | ax7.grid() 156 | ax7.legend() 157 | 158 | 159 | 160 | show() 161 | 162 | # from scipy.integrate import cumtrapz, simps 163 | # def get_end_H(xM19, x): # xM19的数据足够长,而x的不够, 164 | # for ind, el in enumerate(xM19): 165 | # if el > x[-1]: 166 | # print el, x[-1], 'A/m' 167 | # print 'index is', ind 168 | # return ind 169 | # M19_gradient = gradient(yM19) / gradient(xM19) 170 | # # M19 appended to original 171 | # end_index = get_end_H(xM19, x) 172 | # y_fit_gradient_integrate = cumtrapz(M19_gradient[end_index:], xM19[end_index:], initial=0) + y[-1] 173 | # ax1.plot(xM19[end_index:], y_fit_gradient_integrate, 'v-', label='M19-ori', alpha=0.33) 174 | 175 | 176 | 177 | # from scipy import misc 178 | # e_exp = [1.0, 1.0, 1.0/2.0, 1.0/6.0, 1.0/24.0, 1.0/120.0] 179 | # p, q = misc.pade(e_exp, 2) 180 | # '这里的misc.pade需要已知近似对象的泰勒级数,呵呵' 181 | 182 | -------------------------------------------------------------------------------- /codes3/method_parallel_solve_4jmag.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import subprocess 3 | 4 | dir_run = sys.argv[1] 5 | number_of_instantces = int(sys.argv[2]) 6 | 7 | procs = [] 8 | for i in range(number_of_instantces): 9 | proc = subprocess.Popen([sys.executable, 'parasolve.py', 10 | str(i), str(number_of_instantces), dir_run], bufsize=-1) 11 | procs.append(proc) 12 | 13 | for proc in procs: 14 | proc.wait() 15 | 16 | -------------------------------------------------------------------------------- /codes3/method_parasolve_greedy_search.py: -------------------------------------------------------------------------------- 1 | # coding:u8 2 | # method_parasolve_greedy_search 3 | 4 | import sys 5 | import subprocess 6 | 7 | dir_femm_temp = sys.argv[1] 8 | number_of_instantces = int(sys.argv[2]) 9 | str_stack_length = sys.argv[3] 10 | 11 | # debug 12 | # print sys.argv 13 | # import os 14 | # os.system("pause") 15 | # quit() 16 | 17 | # manager 18 | proc = subprocess.Popen([sys.executable, 'parasolve_greedy_search_manager.py', 19 | str(number_of_instantces), dir_femm_temp, str_stack_length], bufsize=-1) 20 | 21 | # 等了也是白等,直接就运行返回了 22 | proc.wait() 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /codes3/mop.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": ".." 5 | } 6 | ], 7 | "settings": { 8 | "makefile.extensionOutputFolder": "./.vscode" 9 | } 10 | } -------------------------------------------------------------------------------- /codes3/parasolve.py: -------------------------------------------------------------------------------- 1 | # coding:u8 2 | # https://stackoverflow.com/questions/19156467/run-multiple-instances-of-python-script-simultaneously 3 | # https://docs.python.org/2/library/subprocess.html#subprocess.Popen 4 | 5 | # wait 6 | # https://stackoverflow.com/questions/100624/python-on-windows-how-to-wait-for-multiple-child-processes 7 | 8 | import os 9 | import sys 10 | import femm 11 | from time import time 12 | 13 | from numpy import exp, pi # sqrt 14 | # from numpy import savetxt, c_ 15 | 16 | def write_Torque_and_B_data_to_file(str_rotor_position, rotation_operator): 17 | # call this after mi_analyze 18 | femm.mi_loadsolution() 19 | 20 | # Physical Amount on the Rotor 21 | femm.mo_groupselectblock(100) # rotor iron 22 | femm.mo_groupselectblock(101) # rotor bars 23 | Fx = femm.mo_blockintegral(18) #-- 18 x (or r) part of steady-state weighted stress tensor force 24 | Fy = femm.mo_blockintegral(19) #--19 y (or z) part of steady-state weighted stress tensor force 25 | torque = femm.mo_blockintegral(22) #-- 22 = Steady-state weighted stress tensor torque 26 | femm.mo_clearblock() 27 | # write results to a data file (write to partial files to avoid compete between parallel instances) 28 | handle_torque.write("%s %g %g %g\n" % ( str_rotor_position, torque, Fx, Fy )) 29 | 30 | # Field Amount of 1/4 model (this is valid if we presume the suspension two pole field is weak) 31 | number_of_elements = femm.mo_numelements() 32 | stator_Bx_data = [] 33 | stator_By_data = [] 34 | stator_Area_data = [] 35 | rotor_Bx_data = [] 36 | rotor_By_data = [] 37 | rotor_Area_data = [] 38 | # one_list = [] 39 | for id_element in range(1, number_of_elements+1): 40 | _, _, _, x, y, area, group = femm.mo_getelement(id_element) 41 | if y>0 and x>0: 42 | if group==10: # stator iron 43 | # 1. What we need for iron loss evaluation is the B waveform at a fixed point (x,y). 44 | # For example, (x,y) is the centeroid of element in stator tooth. 45 | Bx, By = femm.mo_getb(x, y) 46 | stator_Bx_data.append(Bx) 47 | stator_By_data.append(By) 48 | stator_Area_data.append(area) 49 | 50 | if group==100: # rotor iron 51 | # 2. The element at (x,y) is no longer the same element from last rotor position. 52 | # To find the exact element from last rotor position, 53 | # we rotate the (x,y) forward as we rotate the model (rotor), get the B value there: (x,y)*rotation_operator, and correct the (Bx,By)/rotation_operator 54 | complex_new_xy = (x + 1j*y) * rotation_operator 55 | Bx, By = femm.mo_getb( complex_new_xy.real, 56 | complex_new_xy.imag ) 57 | complex_new_BxBy = (Bx + 1j*By) * rotation_operator 58 | rotor_Bx_data.append(complex_new_BxBy.real) 59 | rotor_By_data.append(complex_new_BxBy.imag) 60 | rotor_Area_data.append(area) 61 | 62 | # one_list.append(sqrt(Bx**2 + By**2)) 63 | # one_list.append(area) 64 | # option 1 65 | handle_stator_B_data.write(str_rotor_position + ',' + ','.join(['%g,%g,%g'%(Bx,By,A) for Bx,By,A in zip(stator_Bx_data, stator_By_data, stator_Area_data) ]) + '\n') 66 | handle_rotor_B_data.write(str_rotor_position + ',' + ','.join(['%g,%g,%g'%(Bx,By,A) for Bx,By,A in zip(rotor_Bx_data, rotor_By_data, rotor_Area_data) ]) + '\n') 67 | 68 | # option 2: one_list 69 | # handle_B_data.write(str_rotor_position + ',' + ','.join(['%g'%(B) for B in B_data ]) + ','.join(['%g'%(A) for A in Area_data ]) + '\n') 70 | 71 | # numpy is slower than open().write!!! 72 | # tic = time() 73 | # # savetxt(handle_B_data, c_[one_list]) 74 | # savetxt(handle_B_data, one_list) 75 | # toc = time() 76 | # print toc - tic, 's\n\n' 77 | 78 | femm.mo_close() 79 | 80 | # test 81 | # print '-----------------Testing!!!' 82 | # id_solver = 0 83 | # number_of_instances = 5 84 | # # dir_run = r'D:\femm42\PS_Qr36_NoEndRing_M15_17303l_DPNV\static-femm/' 85 | # dir_run = r'D:\femm42\PS_Qr32_NoEndRing_M19Gauge29_DPNV_1e3Hz\static-femm\sweeping/' 86 | 87 | id_solver = int(sys.argv[1]) 88 | number_of_instances = int(sys.argv[2]) 89 | dir_run = sys.argv[3] 90 | print('ParaSolve', id_solver) 91 | 92 | handle_torque = open(dir_run + "static_results_%d.txt"%(id_solver), 'a') 93 | # handle_stator_B_data = open(dir_run + "static_results_stator_B_data_%d.txt"%(id_solver), 'a') # use 'ab' for np.savetxt https://stackoverflow.com/questions/27786868/python3-numpy-appending-to-a-file-using-numpy-savetxt 94 | # handle_rotor_B_data = open(dir_run + "static_results_rotor_B_data_%d.txt"%(id_solver), 'a') 95 | 96 | fem_file_list = os.listdir(dir_run) 97 | fem_file_list = [f for f in fem_file_list if '.fem' in f] 98 | 99 | femm.openfemm(True) # bHide 100 | femm.callfemm_noeval('smartmesh(0)') 101 | 102 | # this is essential to reduce elements counts from >50000 to ~20000. 103 | print('mi_smartmesh is off') 104 | 105 | for i in range(id_solver, len(fem_file_list), number_of_instances): 106 | 107 | output_file_name = dir_run + fem_file_list[i][:-4] 108 | 109 | if not os.path.exists(output_file_name + '.ans'): 110 | tic = time() 111 | femm.opendocument(output_file_name + '.fem') 112 | # femm.callfemm_noeval('mi_smartmesh(0)') 113 | try: 114 | # femm.mi_createmesh() # [useless] 115 | femm.mi_analyze(1) # None for inherited. 1 for a minimized window, 116 | # print '[debug]', deg_per_step*i*number_of_instances, 'deg' 117 | # rotor_position = deg_per_step*i*number_of_instances / 180. * pi 118 | # write_Torque_and_B_data_to_file(output_file_name[-4:], exp(1j*rotor_position)) # this function is moved to FEMM_Solver.py as keep... 119 | if True: 120 | # call this after mi_analyze 121 | femm.mi_loadsolution() 122 | # Physical Amount on the Rotor 123 | femm.mo_groupselectblock(100) # rotor iron 124 | femm.mo_groupselectblock(101) # rotor bars 125 | Fx = femm.mo_blockintegral(18) #-- 18 x (or r) part of steady-state weighted stress tensor force 126 | Fy = femm.mo_blockintegral(19) #--19 y (or z) part of steady-state weighted stress tensor force 127 | torque = femm.mo_blockintegral(22) #-- 22 = Steady-state weighted stress tensor torque 128 | femm.mo_clearblock() 129 | # write results to a data file (write to partial files to avoid compete between parallel instances) 130 | handle_torque.write("%s %g %g %g\n" % ( output_file_name[-4:], torque, Fx, Fy )) # output_file_name[-4:] = str_rotor_position 131 | # close post-process 132 | femm.mo_close() 133 | except Exception as error: 134 | error.args 135 | raise 136 | # print 'Is it: Material properties have not been defined for all regions? Check the following file:' 137 | # print i, fem_file_list[i] 138 | femm.mi_close() 139 | toc = time() 140 | print(i, fem_file_list[i], toc - tic, 's') 141 | femm.closefemm() 142 | 143 | handle_torque.close() 144 | # handle_B_data.close() 145 | 146 | 147 | # True 148 | # slip_freq_breakdown_torque: 3.0 Hz 149 | # ParaSolve! 150 | # 2 36-0Hz 30.ans 151 | # 6 36-0Hz 90.ans 152 | # 10 36-0Hz 180.fem 153 | # Traceback (most recent call last): 154 | # File "parasolve.py", line 29, in 155 | # Fx = femm.mo_blockintegral(18) #-- 18 x (or r) part of steady-state weighted stress tensor force 156 | # File "D:\Users\horyc\Anaconda2\lib\site-packages\femm\__init__.py", line 1722, in mo_blockintegral 157 | # return callfemm('mo_blockintegral(' + num(ptype) + ')' ); 158 | # File "D:\Users\horyc\Anaconda2\lib\site-packages\femm\__init__.py", line 25, in callfemm 159 | # x = HandleToFEMM.mlab2femm(myString).replace("[ ","[").replace(" ]","]").replace(" ",",").replace("I","1j"); 160 | # File "", line 3, in mlab2femm 161 | # pywintypes.com_error: (-2147023170, 'The remote procedure call failed.', None, None) 162 | -------------------------------------------------------------------------------- /codes3/parasolveSlidingMesh.py: -------------------------------------------------------------------------------- 1 | # coding:u8 2 | # https://stackoverflow.com/questions/19156467/run-multiple-instances-of-python-script-simultaneously 3 | # https://docs.python.org/2/library/subprocess.html#subprocess.Popen 4 | 5 | # wait 6 | # https://stackoverflow.com/questions/100624/python-on-windows-how-to-wait-for-multiple-child-processes 7 | 8 | import os, sys, femm 9 | from time import time 10 | from pylab import np 11 | 12 | id_solver = int(sys.argv[1]) 13 | number_of_parallel_solve = int(sys.argv[2]) 14 | ns = int(sys.argv[3]) # number of steps 15 | output_dir = sys.argv[4] 16 | project_file_name = sys.argv[5] 17 | # GroupSummary = { 18 | # "stator_iron_core" : int(sys.argv[6]), 19 | # "coils" : int(sys.argv[7]), 20 | # "rotor_iron_core" : int(sys.argv[8]), 21 | # "magnet" : int(sys.argv[9]), 22 | # } 23 | GroupSummary = { 24 | "stator_air_gap" : int(sys.argv[6]), 25 | "rotor_air_gap" : int(sys.argv[7]), 26 | "stator_iron_core" : int(sys.argv[8]), 27 | "coils" : int(sys.argv[9]), 28 | "rotor_iron_core" : int(sys.argv[10]), 29 | "magnet" : int(sys.argv[11]), 30 | } 31 | print('[parasolveSlidingMesh.py] ParaSolve instance ID:', id_solver) 32 | 33 | femm.openfemm(True) # bHide 34 | # femm.smartmesh(0) 35 | # femm.callfemm_noeval('smartmesh(0)') 36 | # this is essential to reduce elements counts from >50000 to ~20000. 37 | # print('mi_smartmesh is off') 38 | 39 | bool_initialized = False 40 | parallelResults = {} 41 | for index in range(id_solver, ns, number_of_parallel_solve): 42 | 43 | if os.path.exists(project_file_name[:-4] + f'-{index:03d}.ans'): 44 | msg = 'Previous .ans is not deleted!!! I will now remove it:' + project_file_name[:-4] + f'-{index:03d}.ans' 45 | print(msg) 46 | os.remove(project_file_name[:-4] + f'-{index:03d}.ans') 47 | # raise Exception(msg) 48 | 49 | tic = time() 50 | femm.opendocument(project_file_name[:-4] + f'-{index:03d}.fem') 51 | femm.mi_smartmesh(0) 52 | # femm.mi_saveas(f'temp-{id_solver}.fem') 53 | femm.mi_analyze(1) # None for inherited. 1 for a minimized window, 54 | print(f'{index:02d}', project_file_name[:-4] + f'-{index:03d}.fem | Solving time: {time() - tic:.1f} s ', end='') 55 | femm.mi_loadsolution() 56 | # femm.mo_smooth('off') # flux smoothing algorithm is off 57 | 58 | if bool_initialized==False: 59 | bool_initialized = True 60 | # Record the initial mesh elements if the first time through the loop 61 | nn = int(femm.mo_numelements()) 62 | 63 | M = np.zeros([ns, 22]) # 9 columns of performance data matrix 64 | 65 | z = np.zeros([nn, 1], dtype=np.complex64) # Location of the centroid of each element 66 | a = np.zeros([nn, 1]) # Area of each element 67 | g = np.zeros([nn, 1], dtype=np.int16) # Block label of each element 68 | for m in range(nn): # start from 0 for indexing but from 1 for counting 69 | counting_element = m+1 70 | elm = femm.mo_getelement(counting_element) 71 | # z is a vector of complex numbers that represents the location of the centroid of each element. 72 | z[m] = elm[4-1] + 1j*elm[5-1] 73 | # element area in the length units used to draw the geometry 74 | a[m] = elm[6-1] 75 | # group number associated with the element 76 | g[m] = elm[7-1] 77 | 78 | # mo_getprobleminfo returns, among other things, the depth of the 79 | # machine in the into-the-page direction and the length units used to 80 | # draw the geometry. Both of these pieces of information will be needed 81 | # to integrate the losses over the volume of the machine. 82 | if id_solver == 0: 83 | probinfo = femm.mo_getprobleminfo() 84 | 85 | A = np.zeros([ns, nn]) # matrix that will hold the vector potential info 86 | b = np.zeros([ns, nn], dtype=np.complex64) # matrix that will hold the flux density info 87 | 88 | # Store element flux densities B and magnetic potential A 89 | for m in range(nn): 90 | if g[m]==GroupSummary['magnet']: # Element is in a rotor magnet, marked with group numbers 11 and higher 91 | # Store vector potential at the element centroid for elements that are in PMs 92 | A[index,m] = femm.mo_geta( float(np.real(z[m])), 93 | float(np.imag(z[m])) ) 94 | elif g[m] == GroupSummary['stator_iron_core'] \ 95 | or g[m] == GroupSummary['rotor_iron_core'] \ 96 | or g[m] == GroupSummary['stator_air_gap'] \ 97 | or g[m] == GroupSummary['rotor_air_gap'] \ 98 | or g[m] == GroupSummary['coils']: # Element is on the stator or rotor iron or coils 99 | # Store flux density at the element centroid for these elements 100 | b_temp = femm.mo_getb( float(np.real(z[m])), 101 | float(np.imag(z[m])) ) 102 | b[index,m] = b_temp[0] + 1j*b_temp[1] 103 | 104 | # Torque, force, fluxes 105 | torque = femm.mo_gapintegral('WholeModelSlidingBand',0) 106 | forces = femm.mo_gapintegral('WholeModelSlidingBand',1) 107 | energy = femm.mo_gapintegral('WholeModelSlidingBand',2) 108 | 109 | cP_Uac = femm.mo_getcircuitproperties('U-GrpAC') 110 | cP_Vac = femm.mo_getcircuitproperties('V-GrpAC') 111 | cP_Wac = femm.mo_getcircuitproperties('W-GrpAC') 112 | cP_Ubd = femm.mo_getcircuitproperties('U-GrpBD') 113 | cP_Vbd = femm.mo_getcircuitproperties('V-GrpBD') 114 | cP_Wbd = femm.mo_getcircuitproperties('W-GrpBD') 115 | M[index,0] = torque 116 | M[index,1] = forces[0] 117 | M[index,2] = forces[1] 118 | M[index,3] = energy 119 | M[index,4] = cP_Uac[0] 120 | M[index,5] = cP_Vac[0] 121 | M[index,6] = cP_Wac[0] 122 | M[index,7] = cP_Ubd[0] 123 | M[index,8] = cP_Vbd[0] 124 | M[index,9] = cP_Wbd[0] 125 | M[index,10] = cP_Uac[1] 126 | M[index,11] = cP_Vac[1] 127 | M[index,12] = cP_Wac[1] 128 | M[index,13] = cP_Ubd[1] 129 | M[index,14] = cP_Vbd[1] 130 | M[index,15] = cP_Wbd[1] 131 | M[index,16] = cP_Uac[2] 132 | M[index,17] = cP_Vac[2] 133 | M[index,18] = cP_Wac[2] 134 | M[index,19] = cP_Ubd[2] 135 | M[index,20] = cP_Vbd[2] 136 | M[index,21] = cP_Wbd[2] 137 | femm.mo_close() 138 | femm.mi_close() 139 | toc = time() 140 | print(f'Total time: {toc - tic:.1f} s.') 141 | femm.closefemm() 142 | 143 | parallelResults['b'] = b 144 | parallelResults['A'] = A 145 | parallelResults['M'] = M 146 | # print(id_solver, M) 147 | if id_solver == 0: 148 | parallelResults['z'] = z 149 | parallelResults['a'] = a 150 | parallelResults['g'] = g 151 | parallelResults['probinfo'] = probinfo 152 | import pickle 153 | with open(f'parallelResults{id_solver}.pkl', 'wb') as pickle_file: 154 | pickle.dump(parallelResults, pickle_file, protocol=pickle.HIGHEST_PROTOCOL) 155 | -------------------------------------------------------------------------------- /codes3/parasolve_greedy_search.py: -------------------------------------------------------------------------------- 1 | # coding:u8 2 | 3 | import os 4 | import sys 5 | import femm 6 | from time import time 7 | # from itertools import izip 8 | # from numpy import exp, pi # sqrt 9 | # from numpy import savetxt, c_ 10 | 11 | def get_slipfreq_torque(): 12 | # call this after mi_analyze 13 | femm.mi_loadsolution() 14 | 15 | # Physical Amount on the Rotor 16 | femm.mo_groupselectblock(100) # rotor iron 17 | femm.mo_groupselectblock(101) # rotor bars 18 | # Fx = femm.mo_blockintegral(18) #-- 18 x (or r) part of steady-state weighted stress tensor force 19 | # Fy = femm.mo_blockintegral(19) #--19 y (or z) part of steady-state weighted stress tensor force 20 | torque = femm.mo_blockintegral(22) #-- 22 = Steady-state weighted stress tensor torque 21 | freq = femm.mo_getprobleminfo()[1] 22 | femm.mo_clearblock() 23 | femm.mo_close() 24 | 25 | return freq, torque 26 | 27 | id_solver = int(sys.argv[1]) 28 | dir_femm_temp = sys.argv[2][1:-1] 29 | print('ParaSolve', id_solver, end=' ') 30 | 31 | 32 | femm.openfemm(True) # bHide 33 | # this is essential to reduce elements counts from >50000 to ~20000. 34 | femm.callfemm_noeval('smartmesh(0)') 35 | print('smartmesh is off') 36 | 37 | tic = time() 38 | fem_file_path = dir_femm_temp + 'femm_temp_%d.fem'%(id_solver) 39 | 40 | # # debug 41 | # print fem_file_path 42 | # # print dir_femm_temp 43 | # os.system('pause') 44 | # quit() 45 | 46 | 47 | femm.opendocument(fem_file_path) 48 | counter_loop = 0 49 | while True: 50 | counter_loop += 1 51 | try: 52 | femm.mi_analyze(1) # None for inherited. 1 for a minimized window, 53 | freq, torque = get_slipfreq_torque() 54 | except Exception as error: 55 | print('Error occurs when analyzing the femm file.') 56 | print(error.args) 57 | if counter_loop > 3: 58 | raise error 59 | else: 60 | break 61 | femm.mi_close() 62 | toc = time() 63 | print('id_solver=%d:'% (id_solver), toc - tic, 's') 64 | femm.closefemm() 65 | 66 | # removing files is left for manager to decide 67 | # os.remove(fem_file_path) 68 | # os.remove(fem_file_path[:-4]+'.ans') 69 | 70 | with open(dir_femm_temp + "femm_temp_%d.txt"%(id_solver), 'w') as handle_torque: 71 | # write results to a data file (write to partial files to avoid compete between parallel instances) 72 | handle_torque.write("%g\n%g\n" % ( freq, torque)) 73 | -------------------------------------------------------------------------------- /codes3/parasolve_greedy_search_manager.py: -------------------------------------------------------------------------------- 1 | # coding:u8 2 | # parasolve_greedy_search_manager 3 | 4 | import os 5 | import sys 6 | import femm 7 | from time import time, sleep 8 | import operator 9 | import subprocess 10 | 11 | bool_remove_files = False 12 | 13 | def savetofile(id_solver, freq, stack_length): 14 | femm.mi_probdef(freq, 'millimeters', 'planar', 1e-8, # must < 1e-8 15 | stack_length, 18, 1) # The acsolver parameter (default: 0) specifies which solver is to be used for AC problems: 0 for successive approximation, 1 for Newton. 16 | femm.mi_saveas(dir_femm_temp+'femm_temp_%d.fem'%(id_solver)) 17 | 18 | def remove_files(list_solver_id, dir_femm_temp, suffix, id_solver_femm_found=None): 19 | print('Rename femm file for index', id_solver_femm_found, 'with suffix of', suffix, 'Others will be deleted.') 20 | for id_solver in list_solver_id: 21 | fname = dir_femm_temp + "femm_temp_%d"%(id_solver) + suffix 22 | 23 | if id_solver == id_solver_femm_found: 24 | found_file_name = dir_femm_temp + "femm_found" + suffix 25 | 26 | if os.path.exists(found_file_name): 27 | os.remove(found_file_name) # remove already existing file (due to interrupted run) 28 | 29 | if not os.path.exists(fname): 30 | raise Exception('FEMM file %s does not exist for id_solver=%d'%(fname, id_solver)) 31 | 32 | os.rename(fname, found_file_name) 33 | if bool_remove_files: 34 | continue 35 | else: 36 | break 37 | 38 | # what if we don't remove temp file??? 2019/7/8 39 | if bool_remove_files: 40 | os.remove(fname) 41 | 42 | DEBUG_MODE = False 43 | # if __name__ == '__main__': 44 | # DEBUG_MODE = True 45 | 46 | if DEBUG_MODE == False: 47 | number_of_instantces = int(sys.argv[1]) 48 | dir_femm_temp = sys.argv[2] 49 | stack_length = float(sys.argv[3]) 50 | VAREPSILON = 0.255 # difference between 1st and 2nd max torque slip frequencies 51 | else: 52 | #debug 53 | number_of_instantces = 5 54 | dir_femm_temp = "D:/OneDrive - UW-Madison/c/csv_opti/run#114/femm_temp/" # modify as expected 55 | stack_length = 186.4005899999999940 56 | VAREPSILON = 0.02 57 | 58 | 59 | femm.openfemm(True) 60 | femm.opendocument(dir_femm_temp + 'femm_temp.fem') 61 | 62 | # here, the only variable for an individual is frequency, so pop = list of frequencies 63 | freq_begin = 1. # hz 64 | freq_end = freq_begin + number_of_instantces - 1. # 5 Hz 65 | 66 | list_torque = [] 67 | list_slipfreq = [] 68 | list_solver_id = [] 69 | list_done_id = [] 70 | count_loop = 0 71 | while True: 72 | # freq_step can be negative! 73 | freq_step = (freq_end - freq_begin) / (number_of_instantces-1) 74 | 75 | count_done = len(list_done_id) 76 | if count_done % 5 == 0: 77 | list_solver_id = list(range(0, count_done+number_of_instantces, 1)) 78 | 79 | # parasolve 80 | procs = [] 81 | print('list_solver_id=', list_solver_id) 82 | print('list_solver_id[-number_of_instantces:]=', list_solver_id[-number_of_instantces:]) 83 | for i, id_solver in enumerate(list_solver_id[-number_of_instantces:]): 84 | savetofile(id_solver, freq_begin + i*freq_step, stack_length) 85 | 86 | proc = subprocess.Popen([sys.executable, 'parasolve_greedy_search.py', 87 | str(id_solver), '"'+dir_femm_temp+'"'], bufsize=-1) 88 | procs.append(proc) 89 | 90 | # This wait is working if you run this scirpt from sublime text 91 | for proc in procs: 92 | proc.wait() 93 | 94 | count_sec = 0 95 | while True: 96 | sleep(1) 97 | count_sec += 1 98 | if count_sec > 120: # two min 99 | raise Exception('It is highly likely that exception occurs during the solving of FEMM.') 100 | 101 | print('\nbegin waiting for eddy current solver...') 102 | for id_solver in list_solver_id[-number_of_instantces:]: 103 | 104 | if id_solver in list_done_id: 105 | continue 106 | 107 | fname = dir_femm_temp + "femm_temp_%d.txt"%(id_solver) 108 | # print fname 109 | if os.path.exists(fname): 110 | with open(fname, 'r') as f: 111 | data = f.readlines() 112 | # if data == []: 113 | # sleep(0.1) 114 | # data = f.readlines() 115 | # if data == []: 116 | # raise Exception('What takes you so long to write two float numbers?') 117 | list_slipfreq.append( float(data[0][:-1]) ) 118 | list_torque.append( float(data[1][:-1]) ) 119 | list_done_id.append(id_solver) 120 | 121 | if len(list_done_id) >= number_of_instantces: 122 | break 123 | 124 | # print('-----------------------') 125 | # print list_solver_id 126 | print('slip freq [Hz]:', list_slipfreq) 127 | print('torque [Nm]:', list_torque) 128 | 129 | # find the max 130 | list_torque_copy = list_torque[::] 131 | index_1st, breakdown_torque_1st = max(enumerate(list_torque_copy), key=operator.itemgetter(1)) 132 | breakdown_slipfreq_1st = list_slipfreq[index_1st] 133 | print('The max is #%d'%index_1st, breakdown_torque_1st, 'Nm') 134 | 135 | # 按Eric启发,这里可以添加一个判断,如果1st max是上一步的频率点,比如说 3 Hz 或者 4 Hz,那么说明最大点不在 1st max 和 2nd max 之间,而是在 1st max 和 3rd max之间。 136 | # 按Eric启发,这里可以添加一个判断,如果1st max是上一步的频率点,比如说 3 Hz 或者 4 Hz,那么说明最大点不在 1st max 和 2nd max 之间,而是在 1st max 和 3rd max之间。 137 | # 按Eric启发,这里可以添加一个判断,如果1st max是上一步的频率点,比如说 3 Hz 或者 4 Hz,那么说明最大点不在 1st max 和 2nd max 之间,而是在 1st max 和 3rd max之间。 138 | 139 | # find the 2nd max 140 | list_torque_copy[index_1st] = -999999 141 | index_2nd, breakdown_torque_2nd = max(enumerate(list_torque_copy), key=operator.itemgetter(1)) 142 | breakdown_slipfreq_2nd = list_slipfreq[index_2nd] 143 | print('2nd max is #%d'%index_2nd, breakdown_torque_2nd, 'Nm') 144 | 145 | print('Max slip freq error=', 0.5*(breakdown_slipfreq_1st - breakdown_slipfreq_2nd), 'Hz', ', 2*EPS=%g'%(VAREPSILON)) 146 | 147 | # find the two slip freq close enough then break.5 148 | if abs(breakdown_slipfreq_1st - breakdown_slipfreq_2nd) < VAREPSILON: # Hz 149 | the_index = list_solver_id[index_1st] 150 | print('Found it.', breakdown_slipfreq_1st, 'Hz', breakdown_torque_1st, 'Nm', 'The index is', the_index) 151 | remove_files(list_solver_id, dir_femm_temp, suffix='.fem', id_solver_femm_found=the_index) 152 | remove_files(list_solver_id, dir_femm_temp, suffix='.ans', id_solver_femm_found=the_index) 153 | 154 | 155 | 156 | with open(dir_femm_temp + 'femm_found.csv', 'w') as f: 157 | f.write('%g\n%g\n'%(breakdown_slipfreq_1st, breakdown_torque_1st)) #, Qs_stator_slot_area, Qr_rotor_slot_area)) 158 | break 159 | 160 | else: 161 | print('Not yet.') 162 | count_loop += 1 163 | if count_loop > 100: 164 | os.system('pause') 165 | 166 | # not found yet, try new frequencies. 167 | if breakdown_slipfreq_1st > breakdown_slipfreq_2nd: 168 | if breakdown_slipfreq_1st == list_slipfreq[-1]: 169 | freq_begin = breakdown_slipfreq_1st + 1. 170 | freq_end = freq_begin + number_of_instantces - 1. 171 | else: 172 | freq_begin = breakdown_slipfreq_1st 173 | freq_end = breakdown_slipfreq_2nd 174 | 175 | elif breakdown_slipfreq_1st < breakdown_slipfreq_2nd: 176 | freq_begin = breakdown_slipfreq_1st 177 | freq_end = breakdown_slipfreq_2nd 178 | 179 | # freq_step can be negative! 180 | freq_step = (freq_end - freq_begin) / (2 + number_of_instantces-1) 181 | freq_begin += freq_step 182 | freq_end -= freq_step 183 | print('try: freq_begin=%g, freq_end=%g.' % (freq_begin, freq_end)) 184 | 185 | remove_files(list_solver_id, dir_femm_temp, suffix='.txt') 186 | if bool_remove_files: 187 | os.remove(dir_femm_temp + "femm_temp.fem") 188 | 189 | femm.mi_close() 190 | femm.closefemm() 191 | 192 | if DEBUG_MODE == True: 193 | os.system('pause') 194 | 195 | -------------------------------------------------------------------------------- /codes3/pyx_pole_specific_theory_3D_cages.py: -------------------------------------------------------------------------------- 1 | import PyX_classes, PyX_Utility 2 | import os, pyx 3 | from pylab import np 4 | 5 | ''' TEC-ISMB-2021 三维鼠笼转子 示意图 6 | ''' 7 | if __name__ == '__main__': 8 | pu = PyX_Utility.PyX_Utility() 9 | 10 | PyX_Utility.global_settings['linewidth'] = pyx.style.linewidth.THIck 11 | def RL_Circuit(x, y, voltage_source_label=None, inductance_label=None, resistance_label=None, current_label=None): 12 | ## 反电势作为电压源 13 | U1 = PyX_classes.voltage_source(center=(x, y), bool_vertical=True) 14 | U1.draw(pu, label=voltage_source_label) 15 | ## 电感 16 | L1 = PyX_classes.inductance(location=(x, y+3), bool_vertical=True) 17 | L1.draw(pu, label=inductance_label) 18 | ## 电感 19 | R1 = PyX_classes.resistance(location=(x, y+7), bool_vertical=True) 20 | R1.draw(pu, label=resistance_label) 21 | ## 连接点 22 | PBottom = PyX_classes.connection_point(location=(x, y-3)) 23 | PBottom.draw(pu) 24 | PTop = PyX_classes.connection_point(location=(x, y+11)) 25 | PTop.draw(pu) 26 | ## 连起来 27 | PyX_classes.easy_connect(pu, U1, L1) 28 | PyX_classes.easy_connect(pu, L1, R1) 29 | PyX_classes.easy_connect(pu, U1, PBottom) 30 | PyX_classes.easy_connect(pu, R1, PTop) 31 | 32 | return U1, L1, R1, PTop, PBottom 33 | 34 | x = 0 35 | y = 0 36 | U1, L1, R1, PTop, PBottom = dict(), dict(), dict(), dict(), dict() 37 | U1[0], L1[0], R1[0], PTop[0], PBottom[0] = RL_Circuit(x-6, y+2, voltage_source_label=r'$\bar U_{r1,n}$', inductance_label=r'$L_{r\sigma,n}}$', resistance_label=r'$R_{r,n}}$', current_label=r'\bar I_{r1,n}') 38 | U1[1], L1[1], R1[1], PTop[1], PBottom[1] = RL_Circuit(x+0, y) 39 | U1[2], L1[2], R1[2], PTop[2], PBottom[2] = RL_Circuit(x+5, y) 40 | U1[3], L1[3], R1[3], PTop[3], PBottom[3] = RL_Circuit(x+10, y+2) 41 | PyX_classes.easy_connect(pu, PBottom[0], PBottom[1]) 42 | PyX_classes.easy_connect(pu, PBottom[2], PBottom[1]) 43 | PyX_classes.easy_connect(pu, PBottom[2], PBottom[3]) 44 | PyX_classes.easy_connect(pu, PTop[0], PTop[1]) 45 | PyX_classes.easy_connect(pu, PTop[2], PTop[1]) 46 | PyX_classes.easy_connect(pu, PTop[2], PTop[3]) 47 | 48 | PTop[4] = PyX_classes.connection_point(location=(x-6, y+2+11+3)); PTop[4].draw(pu) 49 | PTop[5] = PyX_classes.connection_point(location=(x+0, y +11+6)); PTop[5].draw(pu) 50 | PTop[6] = PyX_classes.connection_point(location=(x+5, y +11+6)); PTop[6].draw(pu) 51 | PTop[7] = PyX_classes.connection_point(location=(x+10,y+2+11+3)); PTop[7].draw(pu) 52 | 53 | 54 | 55 | ## 输出文件 56 | fname=fr'{os.path.dirname(__file__)}/../release/{os.path.basename(__file__)[:-3]}' 57 | pu.cvs.writePDFfile(fname) 58 | # pu.cvs.writePDFfile(fname) 59 | # pu.cvs.writeSVGfile(fname) 60 | 61 | ## 裁切、打开文件 62 | # os.system(f'sumatraPDF2 {fname}.pdf') 63 | # os.system(f'pdfcrop {fname}.pdf {fname}-crop.pdf') 64 | -------------------------------------------------------------------------------- /codes3/pyx_pole_specific_theory_cage_fields.py: -------------------------------------------------------------------------------- 1 | import sys 2 | print(sys.version) 3 | import PyX_classes, PyX_Utility 4 | import os, pyx 5 | from pylab import np 6 | 7 | ''' TEC-ISMB-2021 鼠笼磁场时域图 8 | ''' 9 | if __name__ == '__main__': 10 | pu = PyX_Utility.PyX_Utility() 11 | 12 | scale = 1.25 13 | x = 17*scale 14 | y = 8*scale 15 | 16 | ## 画坐标图 17 | PyX_Utility.global_settings['linewidth'] = pyx.style.linewidth.thin 18 | PyX_classes.quadrature_coordinate(ylength=y/2+0.5, xlength=x).draw(pu, ymirror=True) 19 | 20 | ## 画波形 21 | PyX_Utility.global_settings['linewidth'] = pyx.style.linewidth.THick 22 | PyX_Utility.global_settings['linestyle'] = pyx.style.linestyle.solid 23 | graph = PyX_classes.function_as_graph() 24 | hat_B_delta_ps = 2 25 | hat_B_delta_p = 4 26 | freq = 0.2 27 | period = 1/freq 28 | def fblue(t): 29 | return - hat_B_delta_ps*np.sin(2*np.pi*(freq) * t ) 30 | def fgreen(t): 31 | return - hat_B_delta_p*np.sin(2*np.pi*(freq*3/4) * t ) 32 | graph.draw( pu, [(t, fblue(t)) for t in np.arange( 0, 4*period+.01, period/40)], settings=['blue'] ) 33 | PyX_Utility.global_settings['linestyle'] = pyx.style.linestyle.dashdotted 34 | PyX_Utility.global_settings['linewidth'] = pyx.style.linewidth.THick 35 | graph.draw( pu, [(t, fgreen(t)) for t in np.arange( 0, 4*period+.01, period/20)], settings=['darkgreen'] ) 36 | 37 | 38 | ## 画导条 39 | PyX_Utility.global_settings['linestyle'] = pyx.style.linestyle.solid 40 | PyX_Utility.global_settings['linewidth'] = pyx.style.linewidth.THIck 41 | radius = 0.5 42 | pu.pyx_circle(radius, center=[period*0.5+0*period, radius], settings=['RawSienna']) 43 | pu.pyx_circle(radius, center=[period*0.5+1*period, radius], settings=['RawSienna']) 44 | pu.pyx_circle(radius, center=[period*0.5+2*period, radius], settings=['RawSienna']) 45 | pu.pyx_circle(radius, center=[period*0.5+3*period, radius], settings=['RawSienna']) 46 | 47 | 48 | ## 标注 49 | # 纵轴 50 | pu.pyx_text([2, y/2], r'$B_\delta(\alpha)$', size=3) 51 | pu.pyx_text([x-0.5, 1.0], r'$\alpha$', size=3) 52 | def xAnnotation(left, right, yline, ytext, label): 53 | pu.pyx_line([left, ytext], 54 | [left, yline]) 55 | pu.pyx_line([right, ytext], 56 | [right, yline]) 57 | pu.pyx_arrow_both_ends( [left, ytext], 58 | [right, ytext]) 59 | pu.pyx_text( [(left+right)/2, ytext+1], label, size=3) 60 | PyX_Utility.global_settings['linewidth'] = pyx.style.linewidth.THick 61 | PyX_Utility.global_settings['linestyle'] = pyx.style.linestyle.dashed 62 | xAnnotation(0.5*period, 1.5*period, 63 | yline = 0, 64 | ytext = -5, 65 | label=r'$\alpha_c$') 66 | 67 | 68 | ## 输出文件 69 | fname=fr'{os.path.dirname(__file__)}/../release/{os.path.basename(__file__)[:-3]}' 70 | pu.cvs.writePDFfile(fname) 71 | # pu.cvs.writePDFfile(fname) 72 | # pu.cvs.writeSVGfile(fname) 73 | 74 | print('save to:', fname) 75 | 76 | ## 裁切、打开文件 77 | os.system(f'sumatraPDF {fname}.pdf') 78 | # os.system(f'pdfcrop {fname}.pdf {fname}-crop.pdf') 79 | -------------------------------------------------------------------------------- /codes3/pyx_pole_specific_theory_cage_phasor_diagram.py: -------------------------------------------------------------------------------- 1 | import PyX_classes, PyX_Utility 2 | import os, pyx 3 | from pylab import np 4 | 5 | ''' TEC-ISMB-2021 鼠笼相量图 6 | ''' 7 | if __name__ == '__main__': 8 | pu = PyX_Utility.PyX_Utility() 9 | 10 | PyX_Utility.global_settings['linewidth'] = pyx.style.linewidth.THIck 11 | 12 | Q_r_prime = 8 13 | alpha_c = 2*np.pi / Q_r_prime 14 | for i in range(Q_r_prime): 15 | radius = 10 16 | x = radius * np.cos(i*alpha_c) 17 | y = radius * -np.sin(i*alpha_c) 18 | pu.pyx_arrow((x,y)) 19 | radius = 9 20 | x = radius * np.cos(i*alpha_c+15/180*np.pi) 21 | y = radius * -np.sin(i*alpha_c+15/180*np.pi) 22 | pu.pyx_text((x,y), r'$\bar U_{r%d,n}$'%(i+1), settings=['blue'], scale=1.5) 23 | radius = 11 24 | x = radius * np.cos(i*alpha_c+0/180*np.pi) 25 | y = radius * -np.sin(i*alpha_c+0/180*np.pi) 26 | pu.pyx_text((x,y), str(i+1), settings=[], scale=1.5) 27 | 28 | radius = 3 29 | x = radius * np.cos(i*alpha_c) 30 | y = radius * -np.sin(i*alpha_c) 31 | pu.pyx_arc((x,y), (radius,0), settings=['earrow']) 32 | radius = 5 33 | x = radius * np.cos(0.5*alpha_c) 34 | y = radius * np.sin(0.5*alpha_c) 35 | pu.pyx_text((x,y), r'$n\alpha_c$', scale=1.5) 36 | 37 | 38 | ## 输出文件 39 | fname=fr'{os.path.dirname(__file__)}/../release/{os.path.basename(__file__)[:-3]}' 40 | pu.cvs.writePDFfile(fname) 41 | # pu.cvs.writePDFfile(fname) 42 | # pu.cvs.writeSVGfile(fname) 43 | 44 | ## 裁切、打开文件 45 | os.system(f'sumatraPDF2 {fname}.pdf') 46 | # os.system(f'pdfcrop {fname}.pdf {fname}-crop.pdf') 47 | -------------------------------------------------------------------------------- /codes3/pyx_pole_specific_theory_cage_phasor_diagram_by_p_ps_fileds.py: -------------------------------------------------------------------------------- 1 | import PyX_classes, PyX_Utility 2 | import os, pyx 3 | from pylab import np 4 | 5 | ''' TEC-ISMB-2021 鼠笼相量图 6 | ''' 7 | if __name__ == '__main__': 8 | pu = PyX_Utility.PyX_Utility() 9 | 10 | PyX_Utility.global_settings['linewidth'] = pyx.style.linewidth.THIck 11 | 12 | pu.pyx_marker((0,0)) 13 | 14 | radius = 10 15 | x = radius 16 | y = 0 17 | pu.pyx_arrow((x,y)) 18 | p, ps = 3, 4 19 | Qr = 20 20 | alpha_c = 360 / Qr 21 | pu.pyx_text((x-3+0, y+1), r'$1,$', scale=1.5) 22 | print(f'alpha={ps*0*alpha_c}') 23 | pu.pyx_text((x-3+1.5, y+1), r'$6,$', scale=1.5) 24 | print(f'alpha={ps*5*alpha_c}') 25 | pu.pyx_text((x-3+0, y-1), r'$11,$', scale=1.5) 26 | print(f'alpha={ps*10*alpha_c}') 27 | pu.pyx_text((x-3+1.5, y-1), r'$16$', scale=1.5) 28 | print(f'alpha={ps*15*alpha_c}') 29 | 30 | 31 | ## 输出文件 32 | fname=fr'{os.path.dirname(__file__)}/../release/{os.path.basename(__file__)[:-3]}a' 33 | pu.cvs.writePDFfile(fname) 34 | # pu.cvs.writePDFfile(fname) 35 | # pu.cvs.writeSVGfile(fname) 36 | 37 | ## 裁切、打开文件 38 | # os.system(f'sumatraPDF2 {fname}.pdf') 39 | # os.system(f'pdfcrop {fname}.pdf {fname}-crop.pdf') 40 | 41 | print('save to:', fname) 42 | 43 | if __name__ == '__main__': 44 | pu = PyX_Utility.PyX_Utility() 45 | 46 | PyX_Utility.global_settings['linewidth'] = pyx.style.linewidth.THIck 47 | 48 | pu.pyx_marker((0,0)) 49 | 50 | radius = 10 51 | x_ori = radius 52 | y_ori = 0 53 | p, ps = 3, 4 54 | Qr = 20 55 | alpha_c = 360 / Qr 56 | print(f'alpha_c={alpha_c}') 57 | 58 | pu.pyx_arrow((x_ori,y_ori)) 59 | pu.pyx_text((x_ori-1.5, y_ori-1), r'$1$', scale=1.5) 60 | print(f'alpha={p*0*alpha_c}') 61 | 62 | def rotate_arrow_and_text(BarNo): 63 | alpha = p*(BarNo-1)*alpha_c / 180 * np.pi 64 | x = x_ori* np.cos(alpha) + y_ori*-np.sin(alpha) 65 | y = x_ori* np.sin(alpha) + y_ori* np.cos(alpha) 66 | pu.pyx_arrow((x,y)) 67 | pu.pyx_text(( np.sign(x)*(abs(x)-1), 68 | np.sign(y)*(abs(y)-1) 69 | ), rf'${BarNo}$', scale=1.5) 70 | print(f'alpha={alpha/np.pi*180}') 71 | 72 | rotate_arrow_and_text(6) 73 | rotate_arrow_and_text(11) 74 | rotate_arrow_and_text(16) 75 | 76 | 77 | ## 输出文件 78 | fname=fr'{os.path.dirname(__file__)}/../release/{os.path.basename(__file__)[:-3]}b' 79 | pu.cvs.writePDFfile(fname) 80 | # pu.cvs.writePDFfile(fname) 81 | # pu.cvs.writeSVGfile(fname) 82 | 83 | ## 裁切、打开文件 84 | # os.system(f'sumatraPDF2 {fname}.pdf') 85 | # os.system(f'pdfcrop {fname}.pdf {fname}-crop.pdf') 86 | 87 | print('save to:', fname) 88 | -------------------------------------------------------------------------------- /codes3/pyx_saturation_time_correction_annotated.py: -------------------------------------------------------------------------------- 1 | import PyX_classes, PyX_Utility 2 | import os, pyx 3 | from pylab import np 4 | 5 | ''' IMIFE saturation time based correction 示意图 6 | ''' 7 | if __name__ == '__main__': 8 | pu = PyX_Utility.PyX_Utility() 9 | 10 | scale = 1.25 11 | x = 20*scale 12 | y = 10*scale 13 | origin = [0, 0] 14 | pt0 = origin 15 | pt1 = [x/4, 0] 16 | pt2 = [x*3/4, 0] 17 | 18 | ell_limit = y/2-0.9 19 | 20 | def text_bias(p, xbias=-1, ybias=1): 21 | return [p[0]+xbias, p[1]+ybias] 22 | 23 | ## 画坐标图 24 | PyX_Utility.global_settings['linewidth'] = pyx.style.linewidth.thin 25 | PyX_classes.quadrature_coordinate(ylength=y/2+0.5, xlength=x).draw(pu, ymirror=True) 26 | 27 | ## 画限幅 28 | PyX_Utility.global_settings['linewidth'] = pyx.style.linewidth.Thick 29 | PyX_Utility.global_settings['linestyle'] = pyx.style.linestyle.dashed 30 | pu.pyx_line([0, ell_limit], [x-8, ell_limit], settings=['RawSienna']) 31 | pu.pyx_line([0, -ell_limit], [x-8, -ell_limit], settings=['RawSienna']) 32 | pu.pyx_text([x-5, ell_limit], r'$\ell=\psi^*$', settings=['RawSienna']) 33 | pu.pyx_text([x-5, -ell_limit], r'$-\ell=-\psi^*$', settings=['RawSienna']) 34 | 35 | ## 波形最大值 36 | psi_mu_max = (y/2-2) 37 | psi_mu_min = -(y/2) 38 | psi_2_max = psi_mu_max 39 | psi_2_min = -ell_limit 40 | pu.pyx_line([0, psi_mu_max], [(pt1[0]-pt0[0])/2, psi_mu_max]) 41 | pu.pyx_text( [-2.2, psi_mu_max], r'$\psi_{\alpha2,\max}$') 42 | pu.pyx_marker([ 0, psi_mu_max]); 43 | ## 波形的最小值 44 | pu.pyx_text( [-2.2, -ell_limit], r'$\psi_{\alpha2,\min}$') #(pt2[0]-pt1[0])/2+pt1[0] 45 | pu.pyx_marker([ 0, -ell_limit]); 46 | 47 | 48 | ## 画波形 49 | PyX_Utility.global_settings['linewidth'] = pyx.style.linewidth.Thick 50 | PyX_Utility.global_settings['linestyle'] = pyx.style.linestyle.solid 51 | graph = PyX_classes.function_as_graph() 52 | def f1(t): 53 | return psi_mu_max*np.sin(2*np.pi*(2/x) * t ) 54 | list_of_saturated_time = [] 55 | def f2(t): 56 | val = psi_mu_min*np.sin(2*np.pi*(1/x) *(t-pt1[0])) 57 | if abs(val) > ell_limit: 58 | list_of_saturated_time.append(t) 59 | return np.sign(val)*ell_limit 60 | else: 61 | return val 62 | graph.draw( pu, [(t, f1(t)) for t in np.arange( 0, pt1[0]+.1, 0.25)] ) 63 | graph.draw( pu, [(t, f2(t)) for t in np.arange(pt1[0], pt2[0]+.1, 0.25)] ) 64 | 65 | ## 画饱和时间 66 | PyX_Utility.global_settings['linewidth'] = pyx.style.linewidth.Thick 67 | PyX_Utility.global_settings['linestyle'] = pyx.style.linestyle.dashed 68 | t_alpha_sat_max = 0 69 | t_alpha_sat_min = max(list_of_saturated_time) - min(list_of_saturated_time) 70 | tSatMinLeft = min(list_of_saturated_time) 71 | tSatMinRight = max(list_of_saturated_time) 72 | def xAnnotation(left, right, yline, ytext, label): 73 | pu.pyx_line([left, ytext], 74 | [left, yline]) 75 | pu.pyx_line([right, ytext], 76 | [right, yline]) 77 | pu.pyx_arrow_both_ends( [left, ytext], 78 | [right, ytext]) 79 | pu.pyx_text( [(left+right)/2, ytext+1], 80 | label ) 81 | xAnnotation(tSatMinLeft, tSatMinRight, 82 | yline = -ell_limit, 83 | ytext = -ell_limit/2, 84 | label=r'$t_{\alpha,\mathrm{sat,min}}$') 85 | 86 | 87 | ## 标注 88 | # 纵轴 89 | pu.pyx_text([0, y/2+1.5], r'$\psi_{\alpha2}$') 90 | 91 | # 时间轴 92 | pu.pyx_marker(pt0); pu.pyx_text(text_bias(pt0), r'$t_0$') 93 | pu.pyx_marker(pt1); pu.pyx_text(text_bias(pt1), r'$t_1$') 94 | pu.pyx_marker(pt2); pu.pyx_text(text_bias(pt2,xbias=1), r'$t_2$') 95 | xAnnotation(pt1[0], pt2[0], 96 | yline = 0, 97 | ytext = 2, 98 | label=r'$\Delta t$') 99 | 100 | # 上饱和时间 101 | # pu.pyx_text([pt1[0]/2+3, ell_limit+1.5], r'$t_{\alpha,\mathrm{sat,max}}=0$') 102 | pu.pyx_text( [x-5, ell_limit+1.5], r'$t_{\alpha,\mathrm{sat,max}}=0$') 103 | 104 | # 重心标注 105 | PyX_Utility.global_settings['linestyle'] = pyx.style.linestyle.solid 106 | pu.pyx_marker([ 0, (psi_2_max+psi_2_min)/2]); 107 | pu.pyx_text( [-2, -2+(psi_2_max+psi_2_min)/2], 108 | r'$\frac{\psi_{\alpha2,\max}+\psi_{\alpha2,\min}}{2}$') 109 | pu.pyx_arrow( [-2.5, -1+(psi_2_max+psi_2_min)/2], 110 | [-0.2,-0.1+(psi_2_max+psi_2_min)/2] ) 111 | # 中分线 112 | PyX_Utility.global_settings['linestyle'] = pyx.style.linestyle.dashdotted 113 | # 竖线 114 | pu.pyx_line([pt1[0]/2, psi_2_max], 115 | [pt1[0]/2, psi_2_min], settings=['tint-red']) 116 | # 横线 117 | pu.pyx_line([0, (psi_2_max+psi_2_min)/2], 118 | [pt1[0]/2, (psi_2_max+psi_2_min)/2], settings=['tint-red']) 119 | # 直角横线 120 | size = 0.5 121 | pu.pyx_line([pt1[0]/2-size, -size+(psi_2_max+psi_2_min)/2], 122 | [pt1[0]/2, -size+(psi_2_max+psi_2_min)/2], settings=['tint-red']) 123 | # 直角竖线 124 | pu.pyx_line([pt1[0]/2-size, -size+(psi_2_max+psi_2_min)/2], 125 | [pt1[0]/2-size, +(psi_2_max+psi_2_min)/2], settings=['tint-red']) 126 | 127 | 128 | 129 | 130 | 131 | 132 | ## 输出文件 133 | fname=fr'{os.path.dirname(__file__)}/../release/pyx_sat_time_corr_annotated' 134 | pu.cvs.writePDFfile(fname) 135 | # pu.cvs.writePDFfile(fname) 136 | # pu.cvs.writeSVGfile(fname) 137 | 138 | ## 裁切、打开文件 139 | os.system(f'sumatraPDF2 {fname}.pdf') 140 | os.system(f'pdfcrop {fname}.pdf {fname}-crop.pdf') 141 | -------------------------------------------------------------------------------- /codes3/pyx_saturation_time_correction_annotated_extended.py: -------------------------------------------------------------------------------- 1 | import PyX_classes, PyX_Utility 2 | import os, pyx 3 | from pylab import np 4 | 5 | ''' IMIFE saturation time based correction 示意图 6 | ''' 7 | if __name__ == '__main__': 8 | pu = PyX_Utility.PyX_Utility() 9 | 10 | scale = 1.25 11 | x = 20*scale 12 | y = 10*scale 13 | origin = [0, 0] 14 | 15 | freq1 = 2.5 16 | freq2 = 1.5 17 | 18 | pt0 = origin 19 | pt1 = [x/freq1/2, 0] 20 | pt2 = [pt1[0]+x/freq2/2, 0] 21 | pt3 = [pt2[0]+x/freq2/2, 0] 22 | 23 | ell_limit = y/2-0.9 24 | 25 | def text_bias(p, xbias=-1, ybias=1): 26 | return [p[0]+xbias, p[1]+ybias] 27 | 28 | ## 画坐标图 29 | PyX_Utility.global_settings['linewidth'] = pyx.style.linewidth.thin 30 | PyX_classes.quadrature_coordinate(ylength=y/2+0.5, xlength=x).draw(pu, ymirror=True, settings=['purple-blue']) 31 | 32 | ## 画限幅 33 | PyX_Utility.global_settings['linewidth'] = pyx.style.linewidth.Thick 34 | PyX_Utility.global_settings['linestyle'] = pyx.style.linestyle.dashed 35 | pu.pyx_line([0, ell_limit], [x-5, ell_limit], settings=['RawSienna']) 36 | pu.pyx_line([0, -ell_limit], [x-8, -ell_limit], settings=['RawSienna']) 37 | pu.pyx_text([x-5+3, ell_limit], r'$\ell=\psi^*$', settings=['RawSienna']) 38 | pu.pyx_text([x-8+3, -ell_limit], r'$-\ell=-\psi^*$', settings=['RawSienna']) 39 | 40 | ## 波形最大值 41 | psi_mu_max = (y/2-2) 42 | psi_mu_min = -(y/2) 43 | psi_2_max = psi_mu_max 44 | psi_2_min = -ell_limit 45 | pu.pyx_line([0, psi_mu_max], [(pt1[0]-pt0[0])/2, psi_mu_max]) 46 | pu.pyx_text( [-2.2, psi_mu_max], r'$\psi_{\alpha2,\max}$') 47 | pu.pyx_marker([ 0, psi_mu_max]); 48 | ## 波形的最小值 49 | pu.pyx_text( [-2.2, -ell_limit], r'$\psi_{\alpha2,\min}$') #(pt2[0]-pt1[0])/2+pt1[0] 50 | pu.pyx_marker([ 0, -ell_limit]); 51 | 52 | 53 | ## 画波形 54 | PyX_Utility.global_settings['linewidth'] = pyx.style.linewidth.Thick 55 | PyX_Utility.global_settings['linestyle'] = pyx.style.linestyle.solid 56 | graph = PyX_classes.function_as_graph() 57 | def f1(t): 58 | return psi_mu_max*np.sin(2*np.pi*(freq1/x) * t ) 59 | list_of_saturated_time_before_t2 = [] 60 | list_of_saturated_time_after_t2 = [] 61 | def f2(t): 62 | val = psi_mu_min*np.sin(2*np.pi*(freq2/x) *(t-pt1[0])) 63 | if abs(val) > ell_limit: 64 | if t ell_limit: 65 | if t10: 105 | break 106 | count += 1 107 | A, keys_not_jsonable = recursive_dictionarify(A, keys_not_jsonable) 108 | print(count, A.__dict__) 109 | print(is_jsonable(A.__dict__)) 110 | print(keys_not_jsonable) 111 | 112 | # Option 2: jsonpickle, see https://stackoverflow.com/questions/3768895/how-to-make-a-class-json-serializable 113 | import json 114 | import jsonpickle 115 | print(varname.nameof(A), ':') 116 | print(json.dumps(json.loads(jsonpickle.encode(A)), indent=4)) 117 | 118 | # obj = jsonpickle.decode(file.read()) 119 | obj = jsonpickle.decode(jsonpickle.encode(A)) 120 | print(dir(obj)) 121 | print(obj.arg) 122 | print(obj.arg.arg) 123 | print(obj.arg.arg.arg) 124 | 125 | # # Option 3: jsonS, see 126 | # import jsons 127 | # a_dict = jsons.dump(A, indent=4) 128 | # print(a_dict) 129 | 130 | # a_str = jsons.dumps(A, indent=4) 131 | # print(a_str) 132 | 133 | 134 | # Option 4: No third-party codes 135 | # ust add to_json method to your class like this: 136 | 137 | # def to_json(self): 138 | # return self.message # or how you want it to be serialized 139 | # And add this code (from this answer), to somewhere at the top of everything: 140 | 141 | # from json import JSONEncoder 142 | 143 | # def _default(self, obj): 144 | # return getattr(obj.__class__, "to_json", _default.default)(obj) 145 | 146 | # _default.default = JSONEncoder().default 147 | # JSONEncoder.default = _default 148 | # This will monkey-patch json module when it's imported so JSONEncoder.default() automatically checks for a special "to_json()" method and uses it to encode the object if found. 149 | 150 | # Just like Onur said, but this time you don't have to update every json.dumps() in your project. 151 | 152 | # share edit follow 153 | # edited Apr 27 '18 at 2:13 154 | 155 | # Samuel Liew♦ 156 | # 61.8k4040 gold badges127127 silver badges205205 bronze badges 157 | # answered Aug 4 '16 at 10:27 158 | 159 | # Fancy John 160 | # 33.4k22 gold badges2020 silver badges2424 bronze badges 161 | # 6 162 | # Big thanks! This is the only answer that allows me to do what I want: be able to serialize an object without changing the existing code. The other methods mostly do not work for me. The object is defined in a third-party library, and the serialization code is third-party too. Changing them will be awkward. With your method, I only need to do TheObject.to_json = my_serializer. – Yongwei Wu Oct 11 '17 at 13:12 163 | 164 | 165 | -------------------------------------------------------------------------------- /codes3/visualize-demo-edit-table.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | import numpy as np 3 | import pandas as pd 4 | 5 | # Randomly fill a dataframe and cache it 6 | @st.cache(allow_output_mutation=True) 7 | def get_dataframe(): 8 | return pd.DataFrame( 9 | np.random.randn(50, 20), 10 | columns=('col %d' % i for i in range(20))) 11 | 12 | 13 | df = get_dataframe() 14 | 15 | # Create row, column, and value inputs 16 | row = st.number_input('row', max_value=df.shape[0]) 17 | col = st.number_input('column', max_value=df.shape[1]) 18 | value = st.number_input('value') 19 | 20 | # Change the entry at (row, col) to the given value 21 | df.values[row][col] = value 22 | 23 | # And display the result! 24 | st.dataframe(df) -------------------------------------------------------------------------------- /codes3/visualize.py: -------------------------------------------------------------------------------- 1 | _=""" Regular Python Packages """ 2 | from pylab import mpl, np, plt 3 | import pandas as pd 4 | import os, json, builtins 5 | 6 | _=""" Streamlit Packages """ 7 | import streamlit as st 8 | # from bokeh.plotting import figure 9 | # from bokeh.palettes import Dark2_5 as palette 10 | # import itertools; colors = itertools.cycle(palette) # bokeh colors has a list of colors which can be used in plots 11 | 12 | _=""" My Visualization Utilities """ 13 | import utility_postprocess 14 | 15 | if __name__ == '__main__': 16 | 17 | # Init session_state 18 | "st.session_state object:", st.session_state 19 | if 'ad_list' not in st.session_state: 20 | st.session_state.ad_list = None 21 | st.session_state.folder_of_collection = None 22 | 23 | ## 侧边栏测试 24 | st.sidebar.header('HEADER') 25 | st.sidebar.selectbox('Test', ('1', '2', '3')) 26 | 27 | ## 标题 28 | builtins.order = [4,3,5,6,2,1,7] # [1,2,3,4,5,6] # 只影响帕累托图里的marker顺序 29 | st.title('Swarm Analyzer and Its Visualization') 30 | st.write(r'''### 2021-03-16''') 31 | 32 | ## 选择优化集合 33 | path2acmop = os.path.abspath(os.path.dirname(__file__) + '/../') 34 | SA = utility_postprocess.SwarmAnalyzer(path2acmop) 35 | last_folder_of_collection = st.session_state.folder_of_collection 36 | st.session_state.folder_of_collection = st.selectbox("Choose a collection:", SA.folders_of_collections, 0) 37 | 38 | 39 | if len(SA.folders_of_collections) > 0: 40 | ## 多项选择电机规格 41 | list_specifications, dict_path2SwarmDataOfTheSpecification, dict_settingsOfTheSpecification = SA.get_swarm_group(folder_of_collection=st.session_state.folder_of_collection) 42 | # print(list_specifications) 43 | selected_specifications = st.multiselect( 44 | "Choose a specification", list_specifications, 45 | [el for el in list_specifications if 'Separate Winding' not in el] 46 | ) 47 | 48 | # if __name__ == '!__main__': 49 | 50 | ## 按照所选的电机规格,显示信息 51 | if not selected_specifications: 52 | st.error("Please select at least one trace.") 53 | 54 | else: 55 | ## 读入 ad_list 并保存到 session_state 56 | # 优点是后面选最优设计的时候快,但是缺点是一旦ad_list或ad代码有变动,由于不是用的st.cache,不会自动刷新,需要重启Streamlit,有时候会忘记,debug不方便。 57 | if st.session_state.ad_list is None or st.session_state.folder_of_collection != last_folder_of_collection: 58 | st.session_state.ad_list = ad_list = SA.get_ad_list(selected_specifications) 59 | # st.session_state.folder_of_collection = last_folder_of_collection 60 | else: 61 | ad_list = st.session_state.ad_list 62 | 63 | for ad in ad_list: 64 | ad.read_swarm_data_json() 65 | 66 | ## 是否显示自动最优个体表格和帕累托前沿? 67 | if st.checkbox("Show Table and Pareto Front."): 68 | # st.checkbox("Great", value = True) 69 | df, fig = SA.inspect_swarm_and_show_table_plus_Pareto_front(ad_list) 70 | st.table(df) # df.style.format("{:.2%}") 71 | st.pyplot(fig) 72 | 73 | ## 根据用户在 text_input 的输入来筛选符合条件的最优个体 74 | if True: 75 | df = SA.select_optimal_designs_manually(st, st.session_state, None, ad_list, selected_specifications) 76 | st.table(df) 77 | 78 | ## 以Json格式保存用户输入到文件 79 | # st.write("Recorded values: ", st.session_state) 80 | # with open(f'{os.path.dirname(__file__)}/global_recovered_values_new.json', 'w') as f: 81 | # json.dump(st.session_state, f, ensure_ascii=False, indent=4) 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | ## 结束 91 | # Streamlit widgets automatically run the script from top to bottom. Since this button is not connected to any other logic, it just causes a plain rerun. 92 | st.button("Re-run") 93 | -------------------------------------------------------------------------------- /codes3/visualize_streamlit_user_session_data.json: -------------------------------------------------------------------------------- 1 | { 2 | } -------------------------------------------------------------------------------- /codes3/where_am_i.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | # self-introspective 4 | def where_am_i_v2(fea_config_dict, bool_post_processing=False): 5 | def get_pc_name(): 6 | import platform 7 | import socket 8 | n1 = platform.node() 9 | n2 = socket.gethostname() 10 | n3 = os.environ["COMPUTERNAME"] 11 | if n1 == n2 == n3: 12 | return n1 13 | elif n1 == n2: 14 | return n1 15 | elif n1 == n3: 16 | return n1 17 | elif n2 == n3: 18 | return n2 19 | else: 20 | raise Exception("Computer names are not equal to each other.") 21 | 22 | # dir_interpreter = os.path.abspath('') + '/' 23 | # if 'DrH' not in dir_interpreter: 24 | # # print(dir_interpreter) 25 | # raise Exception("Revert this back before you run on server.") 26 | # 先用这个救救急,到服务器的时候这里要报错 27 | # dir_interpreter = r'D:\DrH\bopt-python\codes3/' 28 | # dir_parent = dir_interpreter #+ '../' # '../' <- relative path is not always a good option 29 | # if not bool_post_processing: 30 | # if 'codes3' not in dir_interpreter: 31 | # raise Exception('Do not run the script from your current directory: %s.\nPlease cd to ./codes3 before running python scripts.'%(dir_interpreter)) 32 | 33 | # A = os.path.join(os.path.dirname(__file__), '..') 34 | # # A is the parent directory of the directory where program resides. 35 | # B = os.path.dirname(os.path.realpath(__file__)) 36 | # # B is the canonicalised (?) directory where the program resides. 37 | # C = os.path.abspath(os.path.dirname(__file__)) 38 | # # C is the absolute path of the directory where the program resides. 39 | # print(__file__) 40 | # print(os.path.join(os.path.dirname(__file__), '..')) 41 | # print(os.path.dirname(os.path.realpath(__file__))) 42 | # print(os.path.abspath(os.path.dirname(__file__))) 43 | 44 | dir_parent = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) + '/' 45 | dir_codes = os.path.abspath(os.path.dirname(__file__)) + '/' 46 | # dir_femm_files = dir_parent + 'femm_files/' 47 | pc_name = get_pc_name() 48 | os.chdir(dir_codes) 49 | print('[where_am_i.py] CD to:', dir_codes) 50 | 51 | # print(dir_parent, dir_codes, pc_name, sep='\n'); quit() 52 | # fea_config_dict['dir.interpreter'] = dir_interpreter 53 | fea_config_dict['dir.parent'] = dir_parent 54 | # fea_config_dict['dir.codes'] = dir_codes 55 | # fea_config_dict['dir.femm_files'] = dir_parent 56 | fea_config_dict['pc_name'] = pc_name 57 | 58 | fea_config_dict['delete_results_after_calculation'] = False # True for saving disk space (but you lose voltage profile and element data) 59 | if 'designer.OnlyTableResults' in fea_config_dict.keys(): 60 | # if fea_config_dict['designer.Restart'] == False: 61 | # fea_config_dict['designer.OnlyTableResults'] = True # save disk space for my PC 62 | # However, we need field data for iron loss calculation 63 | fea_config_dict['designer.OnlyTableResults'] = False 64 | 65 | 66 | if __name__ == '__main__': 67 | # add path to fea_config_dict 68 | where_am_i_v2(fea_config_dict) 69 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | 2 | ''' Install Anaconda3-2021.05-Windows-x86_64 Python 3.8.8 for PYGMO to work ''' 3 | 4 | # import os 5 | # os.system('cd codes3 && python acmop.py') 6 | import sys 7 | import os 8 | sys.path.append(os.path.abspath('../codes3')) 9 | 10 | from codes3 import JMAG 11 | 12 | import matplotlib 13 | matplotlib.use('TkAgg') # 设置 Matplotlib 后端 14 | import matplotlib.pyplot as plt 15 | import numpy as np 16 | 17 | def main(): 18 | fig, axeses = plt.subplots(2, 2) 19 | time_list = np.linspace(0, 10, 100) 20 | torque = np.sin(time_list) 21 | force_x = np.cos(time_list) 22 | force_y = np.sin(time_list) 23 | force_abs = np.sqrt(force_x**2 + force_y**2) 24 | sfv = type('sfv', (object,), { 25 | 'force_abs': force_abs, 26 | 'force_x': force_x, 27 | 'force_y': force_y, 28 | 'ss_avg_force_magnitude': np.mean(force_abs), 29 | 'normalized_force_error_magnitude': 0.1, 30 | 'ss_max_force_err_abs': [0.2, -0.2], 31 | 'force_error_angle': 5, 32 | 'ss_max_force_err_ang': [3, -3], 33 | 'ss_avg_force_vector': [np.mean(force_x), np.mean(force_y)] 34 | })() 35 | 36 | JMAG.add_plots(axeses, title='Test Plot', label='Test', zorder=1, time_list=time_list, sfv=sfv, torque=torque, range_ss=20) 37 | 38 | plt.show() 39 | 40 | if __name__ == '__main__': 41 | main() 42 | 43 | quit() 44 | 45 | 46 | import os, sys 47 | try:sys.path.insert(0, os.path.dirname(__file__)+'/codes3/') 48 | except:sys.path.insert(0, 'D:/DrH/Codes/acmop/codes3/') 49 | finally:import acmop 50 | 51 | mop = acmop.AC_Machine_Optiomization_Wrapper( 52 | # select_spec='IM Q24p1y9 Qr32 Round Bar', 53 | # select_fea_config_dict = '#019 JMAG IM Nine Variables', 54 | 55 | select_spec = 'PMSM Q12p4y1 PEMD-2020', #'PMSM Q18p4y2 Beijing ShiDaiChaoQun', 56 | select_fea_config_dict = '#02 JMAG PMSM Evaluation Setting', 57 | # select_fea_config_dict = '#04 FEMM PMSM Evaluation Setting', 58 | 59 | project_loc = fr'../_default/', 60 | bool_show_GUI = True 61 | ) 62 | mop.part_evaluation() # Module 3 63 | 64 | 65 | --------------------------------------------------------------------------------