femagtools 1.8.1__py3-none-any.whl → 1.8.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- femagtools/__init__.py +1 -1
- femagtools/dxfsl/area.py +110 -1
- femagtools/dxfsl/areabuilder.py +93 -45
- femagtools/dxfsl/conv.py +5 -0
- femagtools/dxfsl/converter.py +85 -27
- femagtools/dxfsl/fslrenderer.py +5 -4
- femagtools/dxfsl/functions.py +14 -6
- femagtools/dxfsl/geom.py +135 -149
- femagtools/dxfsl/journal.py +1 -1
- femagtools/dxfsl/machine.py +161 -9
- femagtools/dxfsl/shape.py +46 -1
- femagtools/dxfsl/svgparser.py +1 -1
- femagtools/dxfsl/symmetry.py +143 -38
- femagtools/femag.py +64 -61
- femagtools/fsl.py +15 -12
- femagtools/isa7.py +3 -2
- femagtools/machine/__init__.py +5 -4
- femagtools/machine/afpm.py +79 -33
- femagtools/machine/effloss.py +29 -18
- femagtools/machine/sizing.py +192 -13
- femagtools/machine/sm.py +34 -36
- femagtools/machine/utils.py +2 -2
- femagtools/mcv.py +58 -29
- femagtools/model.py +4 -3
- femagtools/multiproc.py +79 -80
- femagtools/parstudy.py +11 -5
- femagtools/plot/nc.py +2 -2
- femagtools/semi_fea.py +108 -0
- femagtools/templates/basic_modpar.mako +0 -3
- femagtools/templates/fe-contr.mako +18 -18
- femagtools/templates/ld_lq_fast.mako +3 -0
- femagtools/templates/mult_cal_fast.mako +3 -0
- femagtools/templates/pm_sym_f_cur.mako +4 -1
- femagtools/templates/pm_sym_fast.mako +3 -0
- femagtools/templates/pm_sym_loss.mako +3 -0
- femagtools/templates/psd_psq_fast.mako +3 -0
- femagtools/templates/torq_calc.mako +3 -0
- femagtools/tks.py +23 -20
- femagtools/zmq.py +213 -0
- {femagtools-1.8.1.dist-info → femagtools-1.8.3.dist-info}/METADATA +3 -3
- {femagtools-1.8.1.dist-info → femagtools-1.8.3.dist-info}/RECORD +49 -47
- {femagtools-1.8.1.dist-info → femagtools-1.8.3.dist-info}/WHEEL +1 -1
- tests/test_afpm.py +15 -6
- tests/test_femag.py +1 -1
- tests/test_fsl.py +4 -4
- tests/test_mcv.py +21 -15
- {femagtools-1.8.1.dist-info → femagtools-1.8.3.dist-info}/LICENSE +0 -0
- {femagtools-1.8.1.dist-info → femagtools-1.8.3.dist-info}/entry_points.txt +0 -0
- {femagtools-1.8.1.dist-info → femagtools-1.8.3.dist-info}/top_level.txt +0 -0
    
        femagtools/mcv.py
    CHANGED
    
    | @@ -8,6 +8,7 @@ import sys | |
| 8 8 | 
             
            import copy
         | 
| 9 9 | 
             
            import logging
         | 
| 10 10 | 
             
            import os.path
         | 
| 11 | 
            +
            import pathlib
         | 
| 11 12 | 
             
            import struct
         | 
| 12 13 | 
             
            import math
         | 
| 13 14 | 
             
            import numpy as np
         | 
| @@ -292,7 +293,12 @@ class Mcv(object): | |
| 292 293 | 
             
                    self.MC1_CW_FREQ_FACTOR = 0.0
         | 
| 293 294 | 
             
                    self.MC1_INDUCTION_FACTOR = 0.0
         | 
| 294 295 | 
             
                    self.MC1_INDUCTION_BETA_FACTOR = 0.0
         | 
| 295 | 
            -
             | 
| 296 | 
            +
                    self.jordan = {}
         | 
| 297 | 
            +
                    # {'ch': 0, 'cw': 0, 'ch_freq':0, 'cw_freq':0}
         | 
| 298 | 
            +
                    self.steinmetz = {}
         | 
| 299 | 
            +
                    # {'ch': 0, 'cw': 0, 'ch_freq':0, 'cw_freq':0}
         | 
| 300 | 
            +
                    self.bertotti = {}
         | 
| 301 | 
            +
                    # {'ch': 0, 'cw': 0, 'ce':0, 'ch_freq':0, 'cw_freq':0}
         | 
| 296 302 | 
             
                    self.MC1_FE_SPEZ_WEIGTH = 7.65
         | 
| 297 303 | 
             
                    self.MC1_FE_SAT_MAGNETIZATION = 2.15
         | 
| 298 304 |  | 
| @@ -335,9 +341,8 @@ class Mcv(object): | |
| 335 341 | 
             
                        self.setData(data)
         | 
| 336 342 |  | 
| 337 343 | 
             
                        self.mc1_curves = len(self.curve)
         | 
| 338 | 
            -
                        if self.mc1_type  | 
| 339 | 
            -
                            self. | 
| 340 | 
            -
                        if self.mc1_type in (ORIENT_CRV, ORIENT_PM_CRV):
         | 
| 344 | 
            +
                        if (self.mc1_type in (ORIENT_CRV, ORIENT_PM_CRV)
         | 
| 345 | 
            +
                            or self.mc1_curves > 1):
         | 
| 341 346 | 
             
                            self.version_mc_curve = self.ORIENTED_VERSION_MC_CURVE
         | 
| 342 347 | 
             
                        elif self.mc1_type == DEMCRV_BR:
         | 
| 343 348 | 
             
                            self.version_mc_curve = self.PARAMETER_PM_CURVE
         | 
| @@ -355,6 +360,9 @@ class Mcv(object): | |
| 355 360 | 
             
                    for k in wtrans:
         | 
| 356 361 | 
             
                        if wtrans[k] in data.keys():
         | 
| 357 362 | 
             
                            self.__setattr__(k, data[wtrans[k]])
         | 
| 363 | 
            +
                    for k in ('bertotti', 'jordan', 'steinmetz'):
         | 
| 364 | 
            +
                        if k in data:
         | 
| 365 | 
            +
                            self.__setattr__(k, data[k])
         | 
| 358 366 | 
             
                    self.curve = data['curve']
         | 
| 359 367 | 
             
                    try:
         | 
| 360 368 | 
             
                        self.mc1_angle = [c['angle'] for c in data['curve']]
         | 
| @@ -364,6 +372,9 @@ class Mcv(object): | |
| 364 372 | 
             
                        self.losses = data['losses']
         | 
| 365 373 | 
             
                    except Exception:
         | 
| 366 374 | 
             
                        pass
         | 
| 375 | 
            +
                    # assume jordan iron loss parameters
         | 
| 376 | 
            +
                    for k in self.jordan:
         | 
| 377 | 
            +
                        self.jordan[k] = getattr(self, transl[k])
         | 
| 367 378 | 
             
                    return
         | 
| 368 379 |  | 
| 369 380 | 
             
                def rtrimValueList(self, vlist):
         | 
| @@ -471,13 +482,25 @@ class Writer(Mcv): | |
| 471 482 | 
             
                                   for c in curve]
         | 
| 472 483 | 
             
                    return curve
         | 
| 473 484 |  | 
| 474 | 
            -
                def writeBinaryFile(self, fillfac=None, recsin=''):
         | 
| 485 | 
            +
                def writeBinaryFile(self, fillfac=None, recsin='', feloss=''):
         | 
| 475 486 | 
             
                    """write binary file after conversion if requested.
         | 
| 476 487 | 
             
                    arguments:
         | 
| 477 488 | 
             
                    fillfac: (float) fill actor
         | 
| 478 489 | 
             
                    recsin: (str) either 'flux' or 'cur'
         | 
| 490 | 
            +
                    feloss: (str) iron loss method (bertotti, jordan)
         | 
| 479 491 | 
             
                    """
         | 
| 480 492 | 
             
                    curve = self._prepare(fillfac, recsin)
         | 
| 493 | 
            +
                    try:
         | 
| 494 | 
            +
                        if feloss.lower() == 'bertotti':
         | 
| 495 | 
            +
                            for k in self.bertotti:
         | 
| 496 | 
            +
                                setattr(self, transl[k], self.bertotti[k])
         | 
| 497 | 
            +
                            del self.losses
         | 
| 498 | 
            +
                        else:
         | 
| 499 | 
            +
                            for k in self.jordan:
         | 
| 500 | 
            +
                                setattr(self, transl[k], self.jordan[k])
         | 
| 501 | 
            +
                    except AttributeError as e:
         | 
| 502 | 
            +
                        logger.warning("%s", e)
         | 
| 503 | 
            +
                        pass
         | 
| 481 504 | 
             
                    mc1_type = self.mc1_type
         | 
| 482 505 | 
             
                    mc1_recalc = self.mc1_recalc
         | 
| 483 506 | 
             
                    mc1_fillfac = self.mc1_fillfac
         | 
| @@ -557,6 +580,7 @@ class Writer(Mcv): | |
| 557 580 |  | 
| 558 581 | 
             
                    try:
         | 
| 559 582 | 
             
                        if not (self.mc1_ch_factor or self.mc1_cw_factor) and self.losses:
         | 
| 583 | 
            +
                            # fit loss parameters
         | 
| 560 584 | 
             
                            pfe = self.losses['pfe']
         | 
| 561 585 | 
             
                            f = self.losses['f']
         | 
| 562 586 | 
             
                            B = self.losses['B']
         | 
| @@ -599,7 +623,8 @@ class Writer(Mcv): | |
| 599 623 | 
             
                        return
         | 
| 600 624 |  | 
| 601 625 | 
             
                    try:
         | 
| 602 | 
            -
                         | 
| 626 | 
            +
                        freq = [x for x in self.losses['f'] if x > 0]
         | 
| 627 | 
            +
                        nfreq = len(freq)
         | 
| 603 628 | 
             
                        nind = len(self.losses['B'])
         | 
| 604 629 | 
             
                        if nind < 1 or nfreq < 1:
         | 
| 605 630 | 
             
                            return
         | 
| @@ -632,26 +657,28 @@ class Writer(Mcv): | |
| 632 657 | 
             
                        self.writeBlock([nfreq, nind])
         | 
| 633 658 | 
             
                        self.writeBlock([float(b) for b in B] + [0.0]*(M_LOSS_INDUCT - nind))
         | 
| 634 659 |  | 
| 660 | 
            +
                        nrec = 0
         | 
| 635 661 | 
             
                        for f, p in zip(self.losses['f'], pfe):
         | 
| 636 662 | 
             
                            if f > 0:
         | 
| 637 663 | 
             
                                y = np.array(p)
         | 
| 638 664 | 
             
                                losses = [float(x) for x in y[y != np.array(None)]]
         | 
| 639 | 
            -
                                 | 
| 640 | 
            -
             | 
| 665 | 
            +
                                nloss = len(losses)
         | 
| 666 | 
            +
                                if nloss == nind:
         | 
| 667 | 
            +
                                    pl = list(p)
         | 
| 641 668 | 
             
                                else:
         | 
| 642 | 
            -
                                    n = len(losses)
         | 
| 643 669 | 
             
                                    cw, alfa, beta = lc.fitsteinmetz(
         | 
| 644 | 
            -
                                        f, B[: | 
| 670 | 
            +
                                        f, B[:nloss], losses, Bo, fo)
         | 
| 645 671 | 
             
                                    pl = losses + [lc.pfe_steinmetz(
         | 
| 646 672 | 
             
                                        f, b, cw, alfa, beta,
         | 
| 647 673 | 
             
                                        self.losses['fo'],
         | 
| 648 674 | 
             
                                        self.losses['Bo'])
         | 
| 649 | 
            -
                                                   for b in B[ | 
| 675 | 
            +
                                                   for b in B[nloss:]]
         | 
| 650 676 | 
             
                                logger.debug("%s", pl)
         | 
| 651 677 | 
             
                                self.writeBlock(pl +
         | 
| 652 678 | 
             
                                                [0.0]*(M_LOSS_INDUCT - len(pl)))
         | 
| 653 679 | 
             
                                self.writeBlock(float(f))
         | 
| 654 | 
            -
             | 
| 680 | 
            +
                                nrec += 1
         | 
| 681 | 
            +
                        for m in range(M_LOSS_FREQ - nrec):
         | 
| 655 682 | 
             
                            self.writeBlock([0.0]*M_LOSS_INDUCT)
         | 
| 656 683 | 
             
                            self.writeBlock(0.0)
         | 
| 657 684 |  | 
| @@ -663,21 +690,21 @@ class Writer(Mcv): | |
| 663 690 | 
             
                    except Exception as e:
         | 
| 664 691 | 
             
                        logger.error("Exception %s", e, exc_info=True)
         | 
| 665 692 |  | 
| 666 | 
            -
                def writeMcv(self, filename, fillfac=None, recsin=''):
         | 
| 693 | 
            +
                def writeMcv(self, filename, fillfac=None, recsin='', feloss='Jordan'):
         | 
| 667 694 | 
             
                    # windows needs this strip to remove '\r'
         | 
| 668 | 
            -
                    filename =  | 
| 669 | 
            -
                    self.name =  | 
| 695 | 
            +
                    filename = pathlib.Path(filename)
         | 
| 696 | 
            +
                    self.name = filename.stem
         | 
| 670 697 |  | 
| 671 | 
            -
                    if filename.upper() | 
| 672 | 
            -
                       filename.upper().endswith('.MC'):
         | 
| 698 | 
            +
                    if filename.suffix.upper() in ('.MCV', '.MC'):
         | 
| 673 699 | 
             
                        binary = True
         | 
| 674 | 
            -
                        self.fp = open( | 
| 700 | 
            +
                        self.fp = filename.open(mode="wb")
         | 
| 675 701 | 
             
                    else:
         | 
| 676 702 | 
             
                        binary = False
         | 
| 677 | 
            -
                        self.fp = open( | 
| 678 | 
            -
                    logger.info("Write File %s, binary format", | 
| 703 | 
            +
                        self.fp = filename.open(mode="w")
         | 
| 704 | 
            +
                    logger.info("Write File %s, binary format (feloss '%s')",
         | 
| 705 | 
            +
                                filename, feloss)
         | 
| 679 706 |  | 
| 680 | 
            -
                    self.writeBinaryFile(fillfac, recsin)
         | 
| 707 | 
            +
                    self.writeBinaryFile(fillfac, recsin, feloss)
         | 
| 681 708 | 
             
                    self.fp.close()
         | 
| 682 709 |  | 
| 683 710 |  | 
| @@ -738,17 +765,16 @@ class Reader(Mcv): | |
| 738 765 |  | 
| 739 766 | 
             
                def readMcv(self, filename):
         | 
| 740 767 | 
             
                    # intens bug : windows needs this strip to remove '\r'
         | 
| 741 | 
            -
                    filename =  | 
| 768 | 
            +
                    filename = pathlib.Path(filename)
         | 
| 742 769 |  | 
| 743 | 
            -
                    if filename. | 
| 744 | 
            -
                       filename.upper().endswith('.MC'):
         | 
| 770 | 
            +
                    if filename.suffix in ('.MCV', '.MC'):
         | 
| 745 771 | 
             
                        binary = True
         | 
| 746 | 
            -
                        self.fp = open( | 
| 772 | 
            +
                        self.fp = filename.open(mode="rb")
         | 
| 747 773 | 
             
                    else:
         | 
| 748 774 | 
             
                        binary = False
         | 
| 749 | 
            -
                        self.fp = open( | 
| 775 | 
            +
                        self.fp = filename.open(mode="r")
         | 
| 750 776 |  | 
| 751 | 
            -
                    self.name =  | 
| 777 | 
            +
                    self.name = filename.stem
         | 
| 752 778 | 
             
                    # read curve version (INTEGER)
         | 
| 753 779 | 
             
                    if binary:
         | 
| 754 780 | 
             
                        self.version_mc_curve = self.readBlock(int)
         | 
| @@ -904,6 +930,8 @@ class Reader(Mcv): | |
| 904 930 | 
             
                    if self.MC1_INDUCTION_FACTOR > 2.0:
         | 
| 905 931 | 
             
                        self.MC1_INDUCTION_FACTOR = 2.0
         | 
| 906 932 |  | 
| 933 | 
            +
                    # TODO: handle self.mc1_ce_factor, self.mc1_induction_beta_factor
         | 
| 934 | 
            +
             | 
| 907 935 | 
             
                    self.losses = {}
         | 
| 908 936 | 
             
                    try:
         | 
| 909 937 | 
             
                        (nfreq, njind) = self.readBlock([int, int])
         | 
| @@ -1086,13 +1114,14 @@ class MagnetizingCurve(object): | |
| 1086 1114 | 
             
                                            repls.items(), name)
         | 
| 1087 1115 |  | 
| 1088 1116 | 
             
                def writefile(self, name, directory='.',
         | 
| 1089 | 
            -
                              fillfac=None, recsin=''):
         | 
| 1117 | 
            +
                              fillfac=None, recsin='', feloss='jordan'):
         | 
| 1090 1118 | 
             
                    """find magnetic curve by name or id and write binary file
         | 
| 1091 1119 | 
             
                    Arguments:
         | 
| 1092 1120 | 
             
                      name: key of mcv dict (name or id)
         | 
| 1093 1121 | 
             
                      directory: destination directory (must be writable)
         | 
| 1094 1122 | 
             
                      fillfac: (float) new fill factor (curves will be recalulated if not None or 0)
         | 
| 1095 1123 | 
             
                      recsin: (str) either 'flux' or 'cur' recalculates for eddy current calculation (dynamic simulation)
         | 
| 1124 | 
            +
                      feloss: (str) iron loss calc method ('jordan', 'bertotti', 'steinmetz')
         | 
| 1096 1125 |  | 
| 1097 1126 | 
             
                    returns filename if found else None
         | 
| 1098 1127 | 
             
                    """
         | 
| @@ -1126,7 +1155,7 @@ class MagnetizingCurve(object): | |
| 1126 1155 | 
             
                    filename = ''.join((bname, ext))
         | 
| 1127 1156 | 
             
                    writer = Writer(mcv)
         | 
| 1128 1157 | 
             
                    writer.writeMcv(os.path.join(directory, filename),
         | 
| 1129 | 
            -
                                    fillfac=fillfac, recsin=recsin)
         | 
| 1158 | 
            +
                                    fillfac=fillfac, recsin=recsin, feloss=feloss)
         | 
| 1130 1159 | 
             
                    return filename
         | 
| 1131 1160 |  | 
| 1132 1161 | 
             
                def fitLossCoeffs(self):
         | 
    
        femagtools/model.py
    CHANGED
    
    | @@ -136,14 +136,15 @@ class MachineModel(Model): | |
| 136 136 | 
             
                    name = 'DRAFT'
         | 
| 137 137 | 
             
                    if isinstance(parameters, str):
         | 
| 138 138 | 
             
                        name = parameters
         | 
| 139 | 
            +
                        self.connect_full = False  # no matter
         | 
| 139 140 | 
             
                    else:
         | 
| 140 141 | 
             
                        if 'name' in parameters:
         | 
| 141 142 | 
             
                            name = parameters['name']
         | 
| 142 143 | 
             
                        if 'windings' in parameters:
         | 
| 143 144 | 
             
                            self.winding = self.windings
         | 
| 144 145 |  | 
| 145 | 
            -
             | 
| 146 | 
            -
             | 
| 146 | 
            +
                        # connect model even for complete model (see fsl connect_models)
         | 
| 147 | 
            +
                        self.connect_full = parameters.get('afmtype', '') == ''
         | 
| 147 148 | 
             
                    # must sanitize name to prevent femag complaints
         | 
| 148 149 | 
             
                    self.name = ''.join([n
         | 
| 149 150 | 
             
                                         for n in name.strip()
         | 
| @@ -410,7 +411,7 @@ class MachineModel(Model): | |
| 410 411 | 
             
                        missing += m
         | 
| 411 412 | 
             
                        names += n
         | 
| 412 413 | 
             
                    if missing:
         | 
| 413 | 
            -
                        raise MCerror(" | 
| 414 | 
            +
                        raise MCerror("Material properties missing: {}".format(
         | 
| 414 415 | 
             
                            ', '.join(set(missing))))
         | 
| 415 416 | 
             
                    return set(names)
         | 
| 416 417 |  | 
    
        femagtools/multiproc.py
    CHANGED
    
    | @@ -12,6 +12,8 @@ import pathlib | |
| 12 12 | 
             
            import logging
         | 
| 13 13 | 
             
            from .job import Job
         | 
| 14 14 | 
             
            import femagtools.config as cfg
         | 
| 15 | 
            +
            import femagtools.zmq
         | 
| 16 | 
            +
            from femagtools.zmq import SubscriberTask
         | 
| 15 17 | 
             
            try:
         | 
| 16 18 | 
             
                from subprocess import DEVNULL
         | 
| 17 19 | 
             
            except ImportError:
         | 
| @@ -19,86 +21,40 @@ except ImportError: | |
| 19 21 |  | 
| 20 22 | 
             
            logger = logging.getLogger(__name__)
         | 
| 21 23 |  | 
| 22 | 
            -
            numpat = re.compile(r'([+-]?\d+(?:\.\d+)?(?:[eE][+-]\d+)?)\s*')
         | 
| 23 | 
            -
             | 
| 24 24 | 
             
            class LicenseError(Exception):
         | 
| 25 25 | 
             
                pass
         | 
| 26 26 |  | 
| 27 | 
            -
            class ProtFile:
         | 
| 28 | 
            -
                def __init__(self, dirname, num_cur_steps):
         | 
| 29 | 
            -
                    self.size = 0
         | 
| 30 | 
            -
                    self.looplen = 0
         | 
| 31 | 
            -
                    self.cur_steps = [1, num_cur_steps]
         | 
| 32 | 
            -
                    self.n = 0
         | 
| 33 | 
            -
                    self.num_loops = 0
         | 
| 34 | 
            -
                    self.dirname = dirname
         | 
| 35 | 
            -
                    self.name = 'samples'
         | 
| 36 | 
            -
             | 
| 37 | 
            -
                def percent(self):
         | 
| 38 | 
            -
                    if self.looplen > 0:
         | 
| 39 | 
            -
                        return min(100 * self.n / self.looplen, 100)
         | 
| 40 | 
            -
                    return 0
         | 
| 41 | 
            -
             | 
| 42 | 
            -
                def update(self):
         | 
| 43 | 
            -
                    p = list(pathlib.Path(self.dirname).glob('*.PROT'))
         | 
| 44 | 
            -
                    if p:
         | 
| 45 | 
            -
                        buf = ''
         | 
| 46 | 
            -
                        if self.size < p[0].stat().st_size:
         | 
| 47 | 
            -
                            with p[0].open() as fp:
         | 
| 48 | 
            -
                                fp.seek(self.size)
         | 
| 49 | 
            -
                                buf = fp.read()
         | 
| 50 | 
            -
                        return self.append(buf)
         | 
| 51 | 
            -
                    return ''
         | 
| 52 | 
            -
             | 
| 53 | 
            -
                def append(self, buf):
         | 
| 54 | 
            -
                    self.size += len(buf)
         | 
| 55 | 
            -
                    for line in [l.strip() for l in buf.split('\n') if l]:
         | 
| 56 | 
            -
                        if line.startswith('Loop'):
         | 
| 57 | 
            -
                            self.n = 0
         | 
| 58 | 
            -
                            try:
         | 
| 59 | 
            -
                                cur_steps = self.cur_steps[self.num_loops]
         | 
| 60 | 
            -
                            except IndexError:
         | 
| 61 | 
            -
                                cur_steps = 1
         | 
| 62 | 
            -
                            x0, x1, dx, nbeta = [float(f)
         | 
| 63 | 
            -
                                                 for f in re.findall(numpat, line)][:4]
         | 
| 64 | 
            -
                            move_steps = round((x1-x0)/dx+1)
         | 
| 65 | 
            -
                            beta_steps = int(nbeta)
         | 
| 66 | 
            -
                            self.looplen = cur_steps*beta_steps*move_steps
         | 
| 67 | 
            -
                            self.num_loops += 1
         | 
| 68 | 
            -
                        elif (line.startswith('Cur') or
         | 
| 69 | 
            -
                              line.startswith('Id')):
         | 
| 70 | 
            -
                            self.n += 1
         | 
| 71 | 
            -
                        elif line.startswith('Number movesteps Fe-Losses'):
         | 
| 72 | 
            -
                            return ''
         | 
| 73 | 
            -
                        elif line.startswith('begin'):
         | 
| 74 | 
            -
                            self.name = line.split()[1].strip()
         | 
| 75 | 
            -
             | 
| 76 | 
            -
                    return f'{self.percent():3.1f}%'  # {self.n}/{self.looplen}'
         | 
| 77 | 
            -
             | 
| 78 | 
            -
             | 
| 79 27 | 
             
            class ProgressLogger(threading.Thread):
         | 
| 80 | 
            -
                def __init__(self, dirs, num_cur_steps, timestep):
         | 
| 28 | 
            +
                def __init__(self, dirs, num_cur_steps, timestep, notify):
         | 
| 81 29 | 
             
                    threading.Thread.__init__(self)
         | 
| 82 | 
            -
                    self. | 
| 83 | 
            -
             | 
| 30 | 
            +
                    self.protfiles = [femagtools.zmq.ProtFile(d, num_cur_steps)
         | 
| 31 | 
            +
                                      for d in dirs]
         | 
| 32 | 
            +
                    self.numTot = len(dirs)
         | 
| 84 33 | 
             
                    self.running = False
         | 
| 85 34 | 
             
                    self.timestep = timestep
         | 
| 35 | 
            +
                    self.notify = notify
         | 
| 86 36 |  | 
| 87 37 | 
             
                def run(self):
         | 
| 88 38 | 
             
                    self.running = True
         | 
| 89 | 
            -
                    protfiles = [ProtFile(d, self.num_cur_steps)
         | 
| 90 | 
            -
                                 for d in self.dirs]
         | 
| 91 39 | 
             
                    while self.running:
         | 
| 92 | 
            -
                         | 
| 93 | 
            -
             | 
| 40 | 
            +
                        if self.timestep > 0:
         | 
| 41 | 
            +
                            time.sleep(self.timestep)
         | 
| 42 | 
            +
                        logmsg = [p.update() for p in self.protfiles]
         | 
| 94 43 | 
             
                        summary = [l  # f'<{i}> {l}'
         | 
| 95 44 | 
             
                                   for i, l in enumerate(logmsg)
         | 
| 96 45 | 
             
                                   if l]
         | 
| 97 46 | 
             
                        if summary:
         | 
| 98 | 
            -
                            labels = set([p.name for p in protfiles])
         | 
| 47 | 
            +
                            labels = set([p.name for p in self.protfiles])
         | 
| 99 48 | 
             
                            logger.info('%s: %s',
         | 
| 100 49 | 
             
                                        ', '.join(labels),
         | 
| 101 50 | 
             
                                        ', '.join(summary))
         | 
| 51 | 
            +
                            if self.notify:
         | 
| 52 | 
            +
                                numOf = f"{summary.count('100.0%')} of {self.numTot}"
         | 
| 53 | 
            +
                                percent = sum([float(i[:-1])
         | 
| 54 | 
            +
                                               for i in summary]) / self.numTot
         | 
| 55 | 
            +
                                self.notify(
         | 
| 56 | 
            +
                                    ["progress_logger",
         | 
| 57 | 
            +
                                     f"{self.numTot}:{numOf}:{percent}:{' '.join(summary)}"])
         | 
| 102 58 | 
             
                        else:
         | 
| 103 59 | 
             
                            logger.info('collecting FE losses ...')
         | 
| 104 60 | 
             
                            return
         | 
| @@ -107,7 +63,7 @@ class ProgressLogger(threading.Thread): | |
| 107 63 | 
             
                    self.running = False
         | 
| 108 64 |  | 
| 109 65 |  | 
| 110 | 
            -
            def run_femag(cmd, workdir, fslfile):
         | 
| 66 | 
            +
            def run_femag(cmd, workdir, fslfile, port):
         | 
| 111 67 | 
             
                """Start the femag command as subprocess.
         | 
| 112 68 |  | 
| 113 69 | 
             
                :internal:
         | 
| @@ -120,7 +76,8 @@ def run_femag(cmd, workdir, fslfile): | |
| 120 76 | 
             
                with open(os.path.join(workdir, "femag.out"), "wb") as out, \
         | 
| 121 77 | 
             
                        open(os.path.join(workdir, "femag.err"), "wb") as err:
         | 
| 122 78 | 
             
                    try:
         | 
| 123 | 
            -
                         | 
| 79 | 
            +
                        args = ['-b', str(port), fslfile] if port else ['-b', fslfile]
         | 
| 80 | 
            +
                        proc = subprocess.Popen(cmd + args,
         | 
| 124 81 | 
             
                                                shell=False,
         | 
| 125 82 | 
             
                                                stdin=DEVNULL,
         | 
| 126 83 | 
             
                                                stdout=out,
         | 
| @@ -163,11 +120,16 @@ class Engine: | |
| 163 120 | 
             
                    cmd: the program (executable image) to be run
         | 
| 164 121 | 
             
                        (femag dc is used if None)
         | 
| 165 122 | 
             
                    process_count: number of processes (cpu_count() if None)
         | 
| 166 | 
            -
                     | 
| 123 | 
            +
                    timestep: time step in seconds for progress log messages if > 0)
         | 
| 167 124 | 
             
                """
         | 
| 168 125 |  | 
| 169 126 | 
             
                def __init__(self, **kwargs):
         | 
| 170 127 | 
             
                    self.process_count = kwargs.get('process_count', None)
         | 
| 128 | 
            +
                    self.notify = kwargs.get('notify', None)
         | 
| 129 | 
            +
                    # cogg_calc mode, subscribe xyplot
         | 
| 130 | 
            +
                    self.calc_mode = kwargs.get('calc_mode')
         | 
| 131 | 
            +
                    self.port = kwargs.get('port', 0)
         | 
| 132 | 
            +
                    self.curve_label = kwargs.get('curve_label')
         | 
| 171 133 | 
             
                    cmd = kwargs.get('cmd', '')
         | 
| 172 134 | 
             
                    if cmd:
         | 
| 173 135 | 
             
                        self.cmd = [cmd]
         | 
| @@ -175,7 +137,10 @@ class Engine: | |
| 175 137 | 
             
                            self.cmd.append('-m')
         | 
| 176 138 |  | 
| 177 139 | 
             
                    self.progressLogger = 0
         | 
| 178 | 
            -
                    self.progress_timestep = kwargs.get('timestep',  | 
| 140 | 
            +
                    self.progress_timestep = kwargs.get('timestep', -1)
         | 
| 141 | 
            +
                    self.subscriber = None
         | 
| 142 | 
            +
                    self.job = None
         | 
| 143 | 
            +
                    self.tasks = []
         | 
| 179 144 |  | 
| 180 145 | 
             
                def create_job(self, workdir):
         | 
| 181 146 | 
             
                    """Create a FEMAG :py:class:`Job`
         | 
| @@ -209,20 +174,41 @@ class Engine: | |
| 209 174 | 
             
                            t.cmd = [cfg.get_executable(
         | 
| 210 175 | 
             
                                t.stateofproblem)] + args
         | 
| 211 176 |  | 
| 212 | 
            -
                     | 
| 213 | 
            -
                     | 
| 214 | 
            -
             | 
| 215 | 
            -
             | 
| 216 | 
            -
             | 
| 217 | 
            -
             | 
| 177 | 
            +
                    num_proc = self.process_count
         | 
| 178 | 
            +
                    if not num_proc and multiprocessing.cpu_count() > 1:
         | 
| 179 | 
            +
                        num_proc = min(multiprocessing.cpu_count()-1, len(self.job.tasks))
         | 
| 180 | 
            +
                    self.pool = multiprocessing.Pool(num_proc)
         | 
| 181 | 
            +
                    if self.port:
         | 
| 182 | 
            +
                        header = [b'progress']
         | 
| 183 | 
            +
                        if self.calc_mode == 'cogg_calc':
         | 
| 184 | 
            +
                            header +=[b'xyplot']
         | 
| 185 | 
            +
                        self.subscriber = [SubscriberTask(port=self.port + i * 5,
         | 
| 186 | 
            +
                                                          host='127.0.0.1',
         | 
| 187 | 
            +
                                                          notify=self.notify,
         | 
| 188 | 
            +
                                                          header=header,
         | 
| 189 | 
            +
                                                          curve_label=self.curve_label,
         | 
| 190 | 
            +
                                                          num_cur_steps=self.job.num_cur_steps,
         | 
| 191 | 
            +
                                                          timestep=self.progress_timestep
         | 
| 192 | 
            +
                                                          )
         | 
| 193 | 
            +
                                           for i, t in enumerate(self.job.tasks)]
         | 
| 194 | 
            +
                        [s.start() for s in self.subscriber]
         | 
| 195 | 
            +
                        self.tasks = [self.pool.apply_async(
         | 
| 196 | 
            +
                            run_femag, args=(t.cmd, t.directory, t.fsl_file, self.port + i * 5))
         | 
| 197 | 
            +
                                      for i, t in enumerate(self.job.tasks)]
         | 
| 198 | 
            +
                    else:
         | 
| 199 | 
            +
                        self.tasks = [self.pool.apply_async(
         | 
| 200 | 
            +
                            run_femag, args=(t.cmd, t.directory, t.fsl_file, 0))
         | 
| 201 | 
            +
                                      for t in self.job.tasks]
         | 
| 218 202 | 
             
                    self.pool.close()
         | 
| 219 203 |  | 
| 220 | 
            -
                     | 
| 221 | 
            -
             | 
| 204 | 
            +
                    # only works on linux
         | 
| 205 | 
            +
                    if (self.progress_timestep and not self.port and
         | 
| 206 | 
            +
                        self.job.num_cur_steps):
         | 
| 222 207 | 
             
                        self.progressLogger = ProgressLogger(
         | 
| 223 208 | 
             
                            [t.directory for t in self.job.tasks],
         | 
| 224 209 | 
             
                            num_cur_steps=self.job.num_cur_steps,
         | 
| 225 | 
            -
                            timestep=self.progress_timestep | 
| 210 | 
            +
                            timestep=self.progress_timestep,
         | 
| 211 | 
            +
                            notify=self.notify)
         | 
| 226 212 | 
             
                        self.progressLogger.start()
         | 
| 227 213 | 
             
                    return len(self.tasks)
         | 
| 228 214 |  | 
| @@ -244,17 +230,30 @@ class Engine: | |
| 244 230 | 
             
                                if t.errmsg:
         | 
| 245 231 | 
             
                                    logger.error(t.errmsg)
         | 
| 246 232 | 
             
                        status.append(t.status)
         | 
| 233 | 
            +
                    self.stopThreads()
         | 
| 234 | 
            +
                    self.pool = None # garbage collector deletes threads
         | 
| 235 | 
            +
                    return status
         | 
| 236 | 
            +
             | 
| 237 | 
            +
                def stopThreads(self):
         | 
| 238 | 
            +
                    """ stop all running treads
         | 
| 239 | 
            +
                    """
         | 
| 247 240 | 
             
                    if self.progressLogger:
         | 
| 248 241 | 
             
                        self.progressLogger.stop()
         | 
| 249 | 
            -
                     | 
| 242 | 
            +
                    if self.port and self.subscriber:
         | 
| 243 | 
            +
                        [s.stop() for s in self.subscriber]
         | 
| 244 | 
            +
                        SubscriberTask.clear()
         | 
| 245 | 
            +
                        self.subscriber = None
         | 
| 250 246 |  | 
| 251 247 | 
             
                def terminate(self):
         | 
| 248 | 
            +
                    """ terminate all
         | 
| 249 | 
            +
                    """
         | 
| 252 250 | 
             
                    logger.info("terminate Engine")
         | 
| 253 | 
            -
                     | 
| 254 | 
            -
             | 
| 251 | 
            +
                    self.stopThreads()
         | 
| 252 | 
            +
             | 
| 255 253 | 
             
                    # terminate pool
         | 
| 256 254 | 
             
                    try:
         | 
| 257 | 
            -
                        self.pool | 
| 258 | 
            -
             | 
| 255 | 
            +
                        if self.pool:
         | 
| 256 | 
            +
                            self.pool.terminate()
         | 
| 257 | 
            +
                            self.pool = None # garbage collector deletes threads
         | 
| 259 258 | 
             
                    except AttributeError as e:
         | 
| 260 259 | 
             
                        logger.warn("%s", e)
         | 
    
        femagtools/parstudy.py
    CHANGED
    
    | @@ -105,10 +105,10 @@ class ParameterStudy(object): | |
| 105 105 | 
             
                        raise ValueError("directory {} is not empty".format(dirname))
         | 
| 106 106 | 
             
                    self.reportdir = dirname
         | 
| 107 107 |  | 
| 108 | 
            -
                def setup_model(self, builder, model, recsin=''):
         | 
| 108 | 
            +
                def setup_model(self, builder, model, recsin='', feloss=''):
         | 
| 109 109 | 
             
                    """builds model in current workdir and returns its filenames"""
         | 
| 110 110 | 
             
                    # get and write mag curves
         | 
| 111 | 
            -
                    mc_files = self.femag.copy_magnetizing_curves(model, recsin=recsin)
         | 
| 111 | 
            +
                    mc_files = self.femag.copy_magnetizing_curves(model, recsin=recsin, feloss=feloss)
         | 
| 112 112 |  | 
| 113 113 | 
             
                    if model.is_complete():
         | 
| 114 114 | 
             
                        logger.info("setup model in %s", self.femag.workdir)
         | 
| @@ -192,7 +192,8 @@ class ParameterStudy(object): | |
| 192 192 | 
             
                                                               objective_vars)
         | 
| 193 193 |  | 
| 194 194 | 
             
                    if immutable_model:
         | 
| 195 | 
            -
                        modelfiles = self.setup_model(builder, model, recsin=fea.recsin | 
| 195 | 
            +
                        modelfiles = self.setup_model(builder, model, recsin=fea.recsin,
         | 
| 196 | 
            +
                                                      feloss=simulation.get('feloss', ''))
         | 
| 196 197 | 
             
                        logger.info("Files %s", modelfiles+extra_files)
         | 
| 197 198 | 
             
                        logger.info("model %s", model.props())
         | 
| 198 199 | 
             
                        for k in ('name', 'poles', 'outer_diam', 'airgap', 'bore_diam',
         | 
| @@ -262,6 +263,10 @@ class ParameterStudy(object): | |
| 262 263 | 
             
                                    p, int(np.ceil(len(par_range)/popsize)),
         | 
| 263 264 | 
             
                                    np.shape(f))
         | 
| 264 265 | 
             
                        job.cleanup()
         | 
| 266 | 
            +
                        try:
         | 
| 267 | 
            +
                            feloss = fea.calc_fe_loss
         | 
| 268 | 
            +
                        except AttributeError:
         | 
| 269 | 
            +
                            feloss = ''
         | 
| 265 270 | 
             
                        for k, x in enumerate(population):
         | 
| 266 271 | 
             
                            task = job.add_task(self.result_func)
         | 
| 267 272 | 
             
                            for fn in extra_files:
         | 
| @@ -284,7 +289,8 @@ class ParameterStudy(object): | |
| 284 289 | 
             
                                for mc in self.femag.copy_magnetizing_curves(
         | 
| 285 290 | 
             
                                        model,
         | 
| 286 291 | 
             
                                        dir=task.directory,
         | 
| 287 | 
            -
                                        recsin=fea.recsin | 
| 292 | 
            +
                                        recsin=fea.recsin,
         | 
| 293 | 
            +
                                        feloss=feloss):
         | 
| 288 294 | 
             
                                    task.add_file(mc)
         | 
| 289 295 | 
             
                                set_magnet_properties(model, fea, self.femag.magnets)
         | 
| 290 296 | 
             
                                task.add_file(
         | 
| @@ -325,7 +331,7 @@ class ParameterStudy(object): | |
| 325 331 | 
             
                                            shutil.copy(ff, repdir)
         | 
| 326 332 | 
             
                                    calcid += 1
         | 
| 327 333 | 
             
                                if isinstance(r, dict) and 'error' in r:
         | 
| 328 | 
            -
                                    logger. | 
| 334 | 
            +
                                    logger.warning("job %d failed: %s", k, r['error'])
         | 
| 329 335 | 
             
                                    if objective_vars:
         | 
| 330 336 | 
             
                                        f.append([float('nan')]*len(objective_vars))
         | 
| 331 337 | 
             
                                    else:
         | 
    
        femagtools/plot/nc.py
    CHANGED
    
    | @@ -14,7 +14,7 @@ DEFAULT_CMAP='viridis' | |
| 14 14 | 
             
            """default colormap (see https://matplotlib.org/stable/users/explain/colors/colormaps.html)"""
         | 
| 15 15 |  | 
| 16 16 |  | 
| 17 | 
            -
            def spel(isa, superelements=[], with_axis=False, ax=0):
         | 
| 17 | 
            +
            def spel(isa, superelements=[], with_axis=False, with_wiredir=False, ax=0):
         | 
| 18 18 | 
             
                """plot super elements of I7/ISA7 model
         | 
| 19 19 | 
             
                Args:
         | 
| 20 20 | 
             
                  isa: Isa7 object
         | 
| @@ -35,7 +35,7 @@ def spel(isa, superelements=[], with_axis=False, ax=0): | |
| 35 35 | 
             
                                        color=isa.color[se.color], lw=0))
         | 
| 36 36 | 
             
                    try:
         | 
| 37 37 | 
             
                        # draw wire direction
         | 
| 38 | 
            -
                        if se.subregion:
         | 
| 38 | 
            +
                        if se.subregion and with_wiredir:
         | 
| 39 39 | 
             
                            if se.subregion.curdir != 0:
         | 
| 40 40 | 
             
                                    wkey = se.subregion.winding.key
         | 
| 41 41 | 
             
                                    if se.subregion.curdir < 0:
         | 
    
        femagtools/semi_fea.py
    ADDED
    
    | @@ -0,0 +1,108 @@ | |
| 1 | 
            +
            import numpy as np 
         | 
| 2 | 
            +
            from .utils import fft
         | 
| 3 | 
            +
            import logging 
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            logger = logging.getLogger('femagtools.semi_fea')
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            def shift_array(v, idx): 
         | 
| 8 | 
            +
                '''shift array by index'''
         | 
| 9 | 
            +
                return v[idx::] + v[0:idx]
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            def fft_filter(result_fft, perc=0.01):
         | 
| 12 | 
            +
                '''filter FFT result with amplitude'''
         | 
| 13 | 
            +
                result = {"order": [], "y":[]}
         | 
| 14 | 
            +
                base_amp = result_fft['a']
         | 
| 15 | 
            +
                for i, j in enumerate(result_fft['nue']):
         | 
| 16 | 
            +
                    if j >= perc*base_amp: 
         | 
| 17 | 
            +
                        result['order'].append(i)
         | 
| 18 | 
            +
                        result['y'].append(j)
         | 
| 19 | 
            +
                return result
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            def fast_skew_cogg(result, skew_setup): 
         | 
| 22 | 
            +
                '''Calculate cogging torque/Back-EMF with step skewing based on unskewed result
         | 
| 23 | 
            +
                Arguments: 
         | 
| 24 | 
            +
                result: BCH objects 
         | 
| 25 | 
            +
                skew_setup(dict): {"skew_angle": 10, "nu_skew_steps": 2}
         | 
| 26 | 
            +
                '''
         | 
| 27 | 
            +
                skew_angle = skew_setup['skew_angle']
         | 
| 28 | 
            +
                num_skew_steps =skew_setup['num_skew_steps']
         | 
| 29 | 
            +
                skew_angle_intern = 0.0
         | 
| 30 | 
            +
                skew_angle_array = []
         | 
| 31 | 
            +
                T_slice = []
         | 
| 32 | 
            +
                bemf = {"1": [], "2": [], "3": []}
         | 
| 33 | 
            +
                bemf_slice = {"1": [], "2": [], "3": []}
         | 
| 34 | 
            +
                bemf_skew = {"1": [], "2": [], "3": []}
         | 
| 35 | 
            +
                bemf_skew_fft = {"1": [], "2": [], "3": []}
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                keyset = ('1', '2', '3')
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                # check if skew steps equals 2
         | 
| 40 | 
            +
                if num_skew_steps == 2: 
         | 
| 41 | 
            +
                    skew_angle_intern = skew_angle
         | 
| 42 | 
            +
                    skew_angle_array = [-skew_angle_intern/2, skew_angle_intern/2]
         | 
| 43 | 
            +
                else: 
         | 
| 44 | 
            +
                    skew_angle_intern = skew_angle/num_skew_steps*(num_skew_steps-1)/2
         | 
| 45 | 
            +
                    skew_angle_array = np.linspace(-skew_angle_intern, skew_angle_intern, 
         | 
| 46 | 
            +
                                                   num_skew_steps, endpoint=True).tolist()
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                angle = result.torque[-1]['angle']
         | 
| 49 | 
            +
                T = result.torque[-1]['torque'][0:-1]
         | 
| 50 | 
            +
                # get back-emf from BCH
         | 
| 51 | 
            +
                for i in keyset: 
         | 
| 52 | 
            +
                    bemf[i] = result.flux[i][-1]['voltage_dpsi'][0:-1]
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                angl_resl = angle[1]
         | 
| 55 | 
            +
                tmp = np.unique(np.abs(skew_angle_array))
         | 
| 56 | 
            +
                skew_angl_resl = 0.0
         | 
| 57 | 
            +
                if np.amin(tmp) == 0.0: 
         | 
| 58 | 
            +
                    skew_angl_resl = tmp[1]
         | 
| 59 | 
            +
                else: 
         | 
| 60 | 
            +
                    skew_angl_resl = tmp[0]
         | 
| 61 | 
            +
                
         | 
| 62 | 
            +
                divider = skew_angl_resl/angl_resl 
         | 
| 63 | 
            +
                if divider - np.floor(divider) > 1e-15: 
         | 
| 64 | 
            +
                    # TODO: Interpolation if angle resolution doesn't match
         | 
| 65 | 
            +
                    logger.warning("Wrong Mesh Size in the airgap mesh")
         | 
| 66 | 
            +
                else: 
         | 
| 67 | 
            +
                    logger.info(f"number of element shifted {divider}")
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                for i in skew_angle_array: 
         | 
| 70 | 
            +
                    idx = int(i/angl_resl)
         | 
| 71 | 
            +
                    if i != 0: 
         | 
| 72 | 
            +
                        T_slice.append(shift_array(T, idx))
         | 
| 73 | 
            +
                        for j in keyset:
         | 
| 74 | 
            +
                            bemf_slice[j].append(shift_array(bemf[j], idx))
         | 
| 75 | 
            +
                    else: 
         | 
| 76 | 
            +
                        # do nothing
         | 
| 77 | 
            +
                        T_slice.append(T)
         | 
| 78 | 
            +
                        for j in keyset:
         | 
| 79 | 
            +
                            bemf_slice[j].append(bemf[j])
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                # average torque
         | 
| 82 | 
            +
                T_sum = 0
         | 
| 83 | 
            +
                for i in T_slice: 
         | 
| 84 | 
            +
                    T_sum += np.array(i)
         | 
| 85 | 
            +
                T_skew = (T_sum/num_skew_steps).tolist()
         | 
| 86 | 
            +
                T_skew += [T_skew[0]]
         | 
| 87 | 
            +
                T_fft = fft_filter(fft(angle, T_skew, pmod=2))
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                # average back-emf
         | 
| 90 | 
            +
                for j in keyset:
         | 
| 91 | 
            +
                    flx_skew = 0
         | 
| 92 | 
            +
                    for k in bemf_slice[j]:
         | 
| 93 | 
            +
                        flx_skew+=np.array(k)
         | 
| 94 | 
            +
                    bemf_skew[j] = (flx_skew/num_skew_steps).tolist()
         | 
| 95 | 
            +
                    bemf_skew[j] += [bemf_skew[j][0]]
         | 
| 96 | 
            +
                    bemf_skew_fft[j] = fft_filter(fft(angle, bemf_skew[j], pmod=2))
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                for i in range(len(T_slice)):
         | 
| 99 | 
            +
                    T_slice[i] = (np.array(T_slice[i])/num_skew_steps).tolist()
         | 
| 100 | 
            +
                    T_slice[i]+=[T_slice[i][0]]
         | 
| 101 | 
            +
                
         | 
| 102 | 
            +
                return {"angle": angle,
         | 
| 103 | 
            +
                        "cogging_torque": T_skew, 
         | 
| 104 | 
            +
                        "cogging_torque_fft": T_fft, 
         | 
| 105 | 
            +
                        "BEMF": bemf_skew, 
         | 
| 106 | 
            +
                        "BEMF_fft": bemf_skew_fft, 
         | 
| 107 | 
            +
                        "cogging_torque_slice": T_slice}
         | 
| 108 | 
            +
                
         | 
| @@ -88,9 +88,6 @@ m.pole_width      = ${model['pole_width']*1e3} | |
| 88 88 | 
             
            % if hasattr(model, 'lfe'):
         | 
| 89 89 | 
             
            m.arm_length      =   ${model.get(['lfe'])*1e3}
         | 
| 90 90 | 
             
            % endif
         | 
| 91 | 
            -
            % if hasattr(model, 'lfe'):
         | 
| 92 | 
            -
            m.arm_length      =   ${model.get(['lfe'])*1e3}
         | 
| 93 | 
            -
            % endif
         | 
| 94 91 | 
             
            % if hasattr(model, 'winding'):
         | 
| 95 92 | 
             
            % if 'num_par_wdgs' in model.winding:
         | 
| 96 93 | 
             
            m.num_par_wdgs    = ${model.winding['num_par_wdgs']}
         |