Esempio n. 1
0
def get_bifurcation_triple_point(p1x, p1d, p2x, p2d, p3x, p3d):
    '''
    Get coordinates and derivatives of triple point between p1, p2 and p3 with derivatives.
    :param p1x..p3d: Point coordinates and derivatives, numbered anticlockwise around triple point.
    All derivatives point away from triple point.
    Returned d1 points from triple point to p2, d2 points from triple point to p3.
    :return: x, d1, d2
    '''
    trx1 = interpolateCubicHermite(p1x, mult(p1d, -2.0), p2x, mult(p2d, 2.0), 0.5)
    trx2 = interpolateCubicHermite(p2x, mult(p2d, -2.0), p3x, mult(p3d, 2.0), 0.5)
    trx3 = interpolateCubicHermite(p3x, mult(p3d, -2.0), p1x, mult(p1d, 2.0), 0.5)
    trx = [ (trx1[c] + trx2[c] + trx3[c])/3.0 for c in range(3) ]
    td1 = interpolateLagrangeHermiteDerivative(trx, p1x, p1d, 0.0)
    td2 = interpolateLagrangeHermiteDerivative(trx, p2x, p2d, 0.0)
    td3 = interpolateLagrangeHermiteDerivative(trx, p3x, p3d, 0.0)
    n12 = cross(td1, td2)
    n23 = cross(td2, td3)
    n31 = cross(td3, td1)
    norm = normalize([ (n12[c] + n23[c] + n31[c]) for c in range(3) ])
    sd1 = smoothCubicHermiteDerivativesLine([ trx, p1x ], [ normalize(cross(norm, cross(td1, norm))), p1d ], fixStartDirection=True, fixEndDerivative=True)[0]
    sd2 = smoothCubicHermiteDerivativesLine([ trx, p2x ], [ normalize(cross(norm, cross(td2, norm))), p2d ], fixStartDirection=True, fixEndDerivative=True)[0]
    sd3 = smoothCubicHermiteDerivativesLine([ trx, p3x ], [ normalize(cross(norm, cross(td3, norm))), p3d ], fixStartDirection=True, fixEndDerivative=True)[0]
    trd1 = mult(sub(sd2, add(sd3, sd1)), 0.5)
    trd2 = mult(sub(sd3, add(sd1, sd2)), 0.5)
    return trx, trd1, trd2
 def smooth(self,
            updateDirections=False,
            maxIterations=10,
            arcLengthTolerance=1.0E-6):
     '''
     :param maxIterations: Maximum iterations before stopping if not converging.
     :param arcLengthTolerance: Ratio of difference in arc length from last iteration
     divided by current arc length under which convergence is achieved. Required to
     be met by every element edge.
     '''
     if not self._derivativeMap:
         return  # no nodes being smoothed
     componentsCount = self._field.getNumberOfComponents()
     with ChangeManager(self._fieldmodule):
         fieldcache = self._fieldmodule.createFieldcache()
         for iter in range(maxIterations + 1):
             converged = True
             for edge in self._edgesMap.values():
                 lastArcLength = edge.getLastArcLength()
                 arcLength = edge.evaluateArcLength(self._nodes,
                                                    self._field, fieldcache)
                 edge.updateLastArcLength()
                 if (math.fabs(arcLength - lastArcLength) /
                         arcLength) > arcLengthTolerance:
                     converged = False
             if converged:
                 print('Derivative smoothing: Converged after', iter,
                       'iterations.')
                 break
             elif (iter == maxIterations):
                 print('Derivative smoothing: Stopping after',
                       maxIterations, 'iterations without converging.')
                 break
             for derivativeKey, derivativeEdges in self._derivativeMap.items(
             ):
                 edgeCount = len(derivativeEdges)
                 if edgeCount > 1:
                     nodeIdentifier, nodeValueLabel, nodeVersion = derivativeKey
                     fieldcache.setNode(
                         self._nodes.findNodeByIdentifier(nodeIdentifier))
                     if updateDirections:
                         x = [0.0 for _ in range(componentsCount)]
                     else:
                         result, x = self._field.getNodeParameters(
                             fieldcache, -1, nodeValueLabel, nodeVersion,
                             componentsCount)
                     mag = 0.0
                     for derivativeEdge in derivativeEdges:
                         edge, expressionIndex, totalScaleFactor = derivativeEdge
                         arcLength = edge.getArcLength()
                         if updateDirections:
                             delta = edge.getDelta()
                             if totalScaleFactor < 0.0:
                                 delta = [-d for d in delta]
                             for c in range(componentsCount):
                                 x[c] += delta[c] / arcLength
                         if self._scalingMode == DerivativeScalingMode.ARITHMETIC_MEAN:
                             mag += arcLength / math.fabs(totalScaleFactor)
                         else:  # self._scalingMode == DerivativeScalingMode.HARMONIC_MEAN
                             mag += math.fabs(totalScaleFactor) / arcLength
                     if self._scalingMode == DerivativeScalingMode.ARITHMETIC_MEAN:
                         mag /= edgeCount
                     else:  # self._scalingMode == DerivativeScalingMode.HARMONIC_MEAN
                         mag = edgeCount / mag
                     if (mag <= 0.0):
                         print('Node', nodeIdentifier, 'value', nodeValueLabel, 'version', nodeVersion, \
                               'has negative mag', mag)
                     x = setMagnitude(x, mag)
                     result = self._field.setNodeParameters(
                         fieldcache, -1, nodeValueLabel, nodeVersion, x)
             for derivativeKey, derivativeEdges in self._derivativeMap.items(
             ):
                 edgeCount = len(derivativeEdges)
                 if edgeCount == 1:
                     # boundary smoothing over single edge
                     nodeIdentifier, nodeValueLabel, nodeVersion = derivativeKey
                     edge, expressionIndex, totalScaleFactor = derivativeEdges[
                         0]
                     # re-evaluate arc length so parameters are up-to-date for other end
                     arcLength = edge.evaluateArcLength(
                         self._nodes, self._field, fieldcache)
                     fieldcache.setNode(
                         self._nodes.findNodeByIdentifier(nodeIdentifier)
                     )  # since changed by evaluateArcLength
                     otherExpressionIndex = 3 if (expressionIndex
                                                  == 1) else 1
                     otherd = edge.getParameter(otherExpressionIndex)
                     if updateDirections:
                         thisx = edge.getParameter(expressionIndex - 1)
                         otherx = edge.getParameter(otherExpressionIndex -
                                                    1)
                         bothEndsOnBoundary = False
                         otherExpression = edge.getExpression(
                             otherExpressionIndex)
                         if len(otherExpression) == 1:
                             otherNodeIdentifier, otherValueLabel, otherNodeVersion, otherTotalScaleFactor = otherExpression[
                                 0]
                             otherDerivativeKey = (otherNodeIdentifier,
                                                   otherValueLabel,
                                                   otherNodeVersion)
                             otherDerivativeEdges = self._derivativeMap.get(
                                 otherDerivativeKey)
                             bothEndsOnBoundary = (
                                 otherDerivativeEdges
                                 is not None) and (len(otherDerivativeEdges)
                                                   == 1)
                         if bothEndsOnBoundary:
                             if expressionIndex == 1:
                                 x = [(otherx[c] - thisx[c])
                                      for c in range(componentsCount)]
                             else:
                                 x = [(thisx[c] - otherx[c])
                                      for c in range(componentsCount)]
                         else:
                             if expressionIndex == 1:
                                 x = interpolateLagrangeHermiteDerivative(
                                     thisx, otherx, otherd, 0.0)
                             else:
                                 x = interpolateHermiteLagrangeDerivative(
                                     otherx, otherd, thisx, 1.0)
                         x = [d / totalScaleFactor for d in x]
                     else:
                         result, x = self._field.getNodeParameters(
                             fieldcache, -1, nodeValueLabel, nodeVersion,
                             componentsCount)
                     othermag = magnitude(otherd)
                     mag = (2.0 * arcLength -
                            othermag) / math.fabs(totalScaleFactor)
                     if (mag <= 0.0):
                         print('Derivative smoothing: Node', nodeIdentifier,
                               'label', nodeValueLabel, 'version',
                               nodeVersion, 'has negative magnitude', mag)
                     x = setMagnitude(x, mag)
                     result = self._field.setNodeParameters(
                         fieldcache, -1, nodeValueLabel, nodeVersion, x)
         # record modified nodes while ChangeManager is in effect
         if self._editNodesetGroup:
             for derivativeKey in self._derivativeMap:
                 self._editNodesetGroup.addNode(
                     (self._nodes.findNodeByIdentifier(derivativeKey[0])))
         del fieldcache
    def generateBaseMesh(cls,
                         region,
                         options,
                         baseCentre=[0.0, 0.0, 0.0],
                         axisSide1=[0.0, -1.0, 0.0],
                         axisUp=[0.0, 0.0, 1.0]):
        """
        Generate the base bicubic-linear Hermite mesh. See also generateMesh().
        Optional extra parameters allow centre and axes to be set.
        :param region: Zinc region to define model in. Must be empty.
        :param options: Dict containing options. See getDefaultOptions().
        :param baseCentre: Centre of valve on ventriculo-arterial junction.
        :param axisSide: Unit vector in first side direction where angle around starts.
        :param axisUp: Unit vector in outflow direction of valve.
        :return: list of AnnotationGroup
         """
        unitScale = options['Unit scale']
        outerHeight = unitScale * options['Outer height']
        innerDepth = unitScale * options['Inner depth']
        cuspHeight = unitScale * options['Cusp height']
        innerRadius = unitScale * 0.5 * options['Inner diameter']
        sinusRadialDisplacement = unitScale * options[
            'Sinus radial displacement']
        wallThickness = unitScale * options['Wall thickness']
        cuspThickness = unitScale * options['Cusp thickness']
        aorticNotPulmonary = options['Aortic not pulmonary']
        useCrossDerivatives = False

        fm = region.getFieldmodule()
        fm.beginChange()
        coordinates = zinc_utils.getOrCreateCoordinateField(fm)
        cache = fm.createFieldcache()

        if aorticNotPulmonary:
            arterialRootGroup = AnnotationGroup(region,
                                                'root of aorta',
                                                FMANumber=3740,
                                                lyphID='Lyph ID unknown')
            cuspGroups = [
                AnnotationGroup(region,
                                'posterior cusp of aortic valve',
                                FMANumber=7253,
                                lyphID='Lyph ID unknown'),
                AnnotationGroup(region,
                                'right cusp of aortic valve',
                                FMANumber=7252,
                                lyphID='Lyph ID unknown'),
                AnnotationGroup(region,
                                'left cusp of aortic valve',
                                FMANumber=7251,
                                lyphID='Lyph ID unknown')
            ]
        else:
            arterialRootGroup = AnnotationGroup(region,
                                                'root of pulmonary trunk',
                                                FMANumber=8612,
                                                lyphID='Lyph ID unknown')
            cuspGroups = [
                AnnotationGroup(region,
                                'right cusp of pulmonary valve',
                                FMANumber=7250,
                                lyphID='Lyph ID unknown'),
                AnnotationGroup(region,
                                'anterior cusp of pulmonary valve',
                                FMANumber=7249,
                                lyphID='Lyph ID unknown'),
                AnnotationGroup(region,
                                'left cusp of pulmonary valve',
                                FMANumber=7247,
                                lyphID='Lyph ID unknown')
            ]

        allGroups = [arterialRootGroup
                     ]  # groups that all elements in scaffold will go in
        annotationGroups = allGroups + cuspGroups

        # annotation fiducial points
        fiducialGroup = zinc_utils.getOrCreateGroupField(fm, 'fiducial')
        fiducialCoordinates = zinc_utils.getOrCreateCoordinateField(
            fm, 'fiducial_coordinates')
        fiducialLabel = zinc_utils.getOrCreateLabelField(fm, 'fiducial_label')
        #fiducialElementXi = zinc_utils.getOrCreateElementXiField(fm, 'fiducial_element_xi')

        datapoints = fm.findNodesetByFieldDomainType(
            Field.DOMAIN_TYPE_DATAPOINTS)
        fiducialPoints = zinc_utils.getOrCreateNodesetGroup(
            fiducialGroup, datapoints)
        datapointTemplateExternal = datapoints.createNodetemplate()
        datapointTemplateExternal.defineField(fiducialCoordinates)
        datapointTemplateExternal.defineField(fiducialLabel)

        #################
        # Create nodes
        #################

        nodes = fm.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_NODES)

        nodetemplate = nodes.createNodetemplate()
        nodetemplate.defineField(coordinates)
        nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                              Node.VALUE_LABEL_VALUE, 1)
        nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                              Node.VALUE_LABEL_D_DS1, 1)
        nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                              Node.VALUE_LABEL_D_DS2, 1)
        nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                              Node.VALUE_LABEL_D_DS3, 1)
        # most nodes in this scaffold do not have a DS3 derivative
        nodetemplateLinearS3 = nodes.createNodetemplate()
        nodetemplateLinearS3.defineField(coordinates)
        nodetemplateLinearS3.setValueNumberOfVersions(coordinates, -1,
                                                      Node.VALUE_LABEL_VALUE,
                                                      1)
        nodetemplateLinearS3.setValueNumberOfVersions(coordinates, -1,
                                                      Node.VALUE_LABEL_D_DS1,
                                                      1)
        nodetemplateLinearS3.setValueNumberOfVersions(coordinates, -1,
                                                      Node.VALUE_LABEL_D_DS2,
                                                      1)
        # several only have a DS1 derivative
        nodetemplateLinearS2S3 = nodes.createNodetemplate()
        nodetemplateLinearS2S3.defineField(coordinates)
        nodetemplateLinearS2S3.setValueNumberOfVersions(
            coordinates, -1, Node.VALUE_LABEL_VALUE, 1)
        nodetemplateLinearS2S3.setValueNumberOfVersions(
            coordinates, -1, Node.VALUE_LABEL_D_DS1, 1)

        nodeIdentifier = max(1, zinc_utils.getMaximumNodeIdentifier(nodes) + 1)

        elementsCountAround = 6
        radiansPerElementAround = 2.0 * math.pi / elementsCountAround
        axisSide2 = vector.crossproduct3(axisUp, axisSide1)
        outerRadius = innerRadius + wallThickness
        cuspOuterLength2 = 0.5 * getApproximateEllipsePerimeter(
            innerRadius, cuspHeight)
        cuspOuterWallArcLength = cuspOuterLength2 * innerRadius / (
            innerRadius + cuspHeight)
        noduleOuterAxialArcLength = cuspOuterLength2 - cuspOuterWallArcLength
        noduleOuterRadialArcLength = innerRadius
        cuspOuterWalld1 = interp.interpolateLagrangeHermiteDerivative(
            [innerRadius, outerHeight + innerDepth - cuspHeight], [0.0, 0.0],
            [-innerRadius, 0.0], 0.0)

        sin60 = math.sin(math.pi / 3.0)
        cuspThicknessLowerFactor = 4.5  # GRC fudge factor
        cuspInnerLength2 = 0.5 * getApproximateEllipsePerimeter(
            innerRadius - cuspThickness / sin60,
            cuspHeight - cuspThicknessLowerFactor * cuspThickness)

        noduleInnerAxialArcLength = cuspInnerLength2 * (
            cuspHeight - cuspThicknessLowerFactor * cuspThickness) / (
                innerRadius - cuspThickness / sin60 + cuspHeight -
                cuspThicknessLowerFactor * cuspThickness)
        noduleInnerRadialArcLength = innerRadius - cuspThickness / math.tan(
            math.pi / 3.0)
        nMidCusp = 0 if aorticNotPulmonary else 1

        # lower points
        ix, id1 = createCirclePoints(
            [(baseCentre[c] - axisUp[c] * innerDepth) for c in range(3)],
            [axisSide1[c] * innerRadius for c in range(3)],
            [axisSide2[c] * innerRadius
             for c in range(3)], elementsCountAround)
        ox, od1 = getSemilunarValveSinusPoints(baseCentre,
                                               axisSide1,
                                               axisSide2,
                                               outerRadius,
                                               sinusRadialDisplacement,
                                               startMidCusp=aorticNotPulmonary)
        lowerx, lowerd1 = [ix, ox], [id1, od1]

        # upper points
        topCentre = [(baseCentre[c] + axisUp[c] * outerHeight)
                     for c in range(3)]
        # twice as many on inner:
        ix, id1 = createCirclePoints(
            topCentre, [axisSide1[c] * innerRadius for c in range(3)],
            [axisSide2[c] * innerRadius
             for c in range(3)], elementsCountAround * 2)
        # tweak inner points so elements attached to cusps are narrower
        cuspRadiansFactor = 0.25  # GRC fudge factor
        midDerivativeFactor = 1.0 + 0.5 * (1.0 - cuspRadiansFactor
                                           )  # GRC test compromise
        cuspAttachmentRadians = cuspRadiansFactor * radiansPerElementAround
        cuspAttachmentRadialDisplacement = wallThickness * 0.333  # GRC fudge factor
        cuspAttachmentRadius = innerRadius - cuspAttachmentRadialDisplacement
        for cusp in range(3):
            n1 = cusp * 2 - 1 + nMidCusp
            n2 = n1 * 2
            id1[n2 + 2] = [2.0 * d for d in id1[n2 + 2]]
            # side 1
            radiansAround = n1 * radiansPerElementAround + cuspAttachmentRadians
            rcosRadiansAround = cuspAttachmentRadius * math.cos(radiansAround)
            rsinRadiansAround = cuspAttachmentRadius * math.sin(radiansAround)
            ix[n2 + 1] = [(topCentre[c] + rcosRadiansAround * axisSide1[c] +
                           rsinRadiansAround * axisSide2[c]) for c in range(3)]
            id1[n2 + 1] = interp.interpolateLagrangeHermiteDerivative(
                ix[n2 + 1], ix[n2 + 2], id1[n2 + 2], 0.0)
            # side 2
            n1 = ((cusp + 1) * 2 - 1 + nMidCusp) % elementsCountAround
            n2 = n1 * 2
            radiansAround = n1 * radiansPerElementAround - cuspAttachmentRadians
            rcosRadiansAround = cuspAttachmentRadius * math.cos(radiansAround)
            rsinRadiansAround = cuspAttachmentRadius * math.sin(radiansAround)
            ix[n2 - 1] = [(topCentre[c] + rcosRadiansAround * axisSide1[c] +
                           rsinRadiansAround * axisSide2[c]) for c in range(3)]
            id1[n2 - 1] = interp.interpolateHermiteLagrangeDerivative(
                ix[n2 - 2], id1[n2 - 2], ix[n2 - 1], 1.0)
        ox, od1 = createCirclePoints(
            topCentre, [axisSide1[c] * outerRadius for c in range(3)],
            [axisSide2[c] * outerRadius
             for c in range(3)], elementsCountAround)
        upperx, upperd1 = [ix, ox], [id1, od1]

        # get lower and upper derivative 2
        zero = [0.0, 0.0, 0.0]
        upperd2factor = outerHeight
        upd2 = [d * upperd2factor for d in axisUp]
        lowerOuterd2 = interp.smoothCubicHermiteDerivativesLine(
            [lowerx[1][nMidCusp], upperx[1][nMidCusp]], [upd2, upd2],
            fixStartDirection=True,
            fixEndDerivative=True)[0]
        lowerd2factor = 2.0 * (outerHeight + innerDepth) - upperd2factor
        lowerInnerd2 = [d * lowerd2factor for d in axisUp]
        lowerd2 = [[lowerInnerd2] * elementsCountAround,
                   [lowerOuterd2] * elementsCountAround
                   ]  # some lowerd2[0] to be fitted below
        upperd2 = [[upd2] * (elementsCountAround * 2),
                   [upd2] * elementsCountAround]

        # get lower and upper derivative 1 or 2 pointing to/from cusps
        for n1 in range(elementsCountAround):
            radiansAround = n1 * radiansPerElementAround
            cosRadiansAround = math.cos(radiansAround)
            sinRadiansAround = math.sin(radiansAround)
            if (n1 % 2) == nMidCusp:
                lowerd2[0][n1] = [
                    -cuspOuterWallArcLength *
                    (cosRadiansAround * axisSide1[c] +
                     sinRadiansAround * axisSide2[c]) for c in range(3)
                ]
            else:
                upperd1[0][n1 * 2] = [
                    (cuspOuterWalld1[0] * (cosRadiansAround * axisSide1[c] +
                                           sinRadiansAround * axisSide2[c]) +
                     cuspOuterWalld1[1] * axisUp[c]) for c in range(3)
                ]

        # inner wall and mid sinus points; only every second one is used
        sinusDepth = innerDepth - cuspThicknessLowerFactor * cuspThickness  # GRC test
        sinusCentre = [(baseCentre[c] - sinusDepth * axisUp[c])
                       for c in range(3)]
        sinusx, sinusd1 = createCirclePoints(
            sinusCentre, [axisSide1[c] * innerRadius for c in range(3)],
            [axisSide2[c] * innerRadius
             for c in range(3)], elementsCountAround)
        # get sinusd2, parallel to lower inclined lines
        sd2 = interp.smoothCubicHermiteDerivativesLine(
            [[innerRadius, -sinusDepth], [innerRadius, outerHeight]],
            [[wallThickness + sinusRadialDisplacement, innerDepth],
             [0.0, upperd2factor]],
            fixStartDirection=True,
            fixEndDerivative=True)[0]
        sinusd2 = [None] * elementsCountAround
        for cusp in range(3):
            n1 = cusp * 2 + nMidCusp
            radiansAround = n1 * radiansPerElementAround
            cosRadiansAround = math.cos(radiansAround)
            sinRadiansAround = math.sin(radiansAround)
            sinusd2[n1] = [(sd2[0] * (cosRadiansAround * axisSide1[c] +
                                      sinRadiansAround * axisSide2[c]) +
                            sd2[1] * axisUp[c]) for c in range(3)]

        # get points on arc between mid sinus and upper cusp points
        arcx = []
        arcd1 = []
        scaled1 = 2.5  # GRC fudge factor
        for cusp in range(3):
            n1 = cusp * 2 + nMidCusp
            n1m = n1 - 1
            n1p = (n1 + 1) % elementsCountAround
            n2m = n1m * 2 + 1
            n2p = n1p * 2 - 1
            ax, ad1 = interp.sampleCubicHermiteCurves(
                [upperx[0][n2m], sinusx[n1]],
                [[-scaled1 * d for d in upperd2[0][n2m]],
                 [scaled1 * d for d in sinusd1[n1]]],
                elementsCountOut=2,
                addLengthStart=0.5 * vector.magnitude(upperd2[0][n2m]),
                lengthFractionStart=0.5,
                addLengthEnd=0.5 * vector.magnitude(sinusd1[n1]),
                lengthFractionEnd=0.5,
                arcLengthDerivatives=False)[0:2]
            arcx.append(ax[1])
            arcd1.append(ad1[1])
            ax, ad1 = interp.sampleCubicHermiteCurves(
                [
                    sinusx[n1],
                    upperx[0][n2p],
                ], [[scaled1 * d for d in sinusd1[n1]],
                    [scaled1 * d for d in upperd2[0][n2p]]],
                elementsCountOut=2,
                addLengthStart=0.5 * vector.magnitude(sinusd1[n1]),
                lengthFractionStart=0.5,
                addLengthEnd=0.5 * vector.magnitude(upperd2[0][n2p]),
                lengthFractionEnd=0.5,
                arcLengthDerivatives=False)[0:2]
            arcx.append(ax[1])
            arcd1.append(ad1[1])
        if nMidCusp == 0:
            arcx.append(arcx.pop(0))
            arcd1.append(arcd1.pop(0))

        # cusp nodule points
        noduleCentre = [(baseCentre[c] + axisUp[c] * (cuspHeight - innerDepth))
                        for c in range(3)]
        nodulex = [[], []]
        noduled1 = [[], []]
        noduled2 = [[], []]
        noduled3 = [[], []]
        cuspRadialThickness = cuspThickness / sin60
        for i in range(3):
            nodulex[0].append(noduleCentre)
            n1 = i * 2 + nMidCusp
            radiansAround = n1 * radiansPerElementAround
            cosRadiansAround = math.cos(radiansAround)
            sinRadiansAround = math.sin(radiansAround)
            nodulex[1].append([(noduleCentre[c] + cuspRadialThickness *
                                (cosRadiansAround * axisSide1[c] +
                                 sinRadiansAround * axisSide2[c]))
                               for c in range(3)])
            n1 = i * 2 - 1 + nMidCusp
            radiansAround = n1 * radiansPerElementAround
            cosRadiansAround = math.cos(radiansAround)
            sinRadiansAround = math.sin(radiansAround)
            noduled1[0].append([
                noduleOuterRadialArcLength * (cosRadiansAround * axisSide1[c] +
                                              sinRadiansAround * axisSide2[c])
                for c in range(3)
            ])
            noduled1[1].append(
                vector.setMagnitude(noduled1[0][i],
                                    noduleInnerRadialArcLength))
            n1 = i * 2 + 1 + nMidCusp
            radiansAround = n1 * radiansPerElementAround
            cosRadiansAround = math.cos(radiansAround)
            sinRadiansAround = math.sin(radiansAround)
            noduled2[0].append([
                noduleOuterRadialArcLength * (cosRadiansAround * axisSide1[c] +
                                              sinRadiansAround * axisSide2[c])
                for c in range(3)
            ])
            noduled2[1].append(
                vector.setMagnitude(noduled2[0][i],
                                    noduleInnerRadialArcLength))
            noduled3[0].append(
                [noduleOuterAxialArcLength * axisUp[c] for c in range(3)])
            noduled3[1].append(
                [noduleInnerAxialArcLength * axisUp[c] for c in range(3)])

        # Create nodes

        lowerNodeId = [[], []]
        for n3 in range(2):
            for n1 in range(elementsCountAround):
                node = nodes.createNode(nodeIdentifier, nodetemplateLinearS3)
                cache.setNode(node)
                coordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_VALUE, 1,
                                              lowerx[n3][n1])
                coordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_D_DS1, 1,
                                              lowerd1[n3][n1])
                coordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_D_DS2, 1,
                                              lowerd2[n3][n1])
                lowerNodeId[n3].append(nodeIdentifier)
                nodeIdentifier += 1

        sinusNodeId = []
        for n1 in range(elementsCountAround):
            if (n1 % 2) != nMidCusp:
                sinusNodeId.append(None)
                continue
            node = nodes.createNode(nodeIdentifier, nodetemplateLinearS3)
            cache.setNode(node)
            coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1,
                                          sinusx[n1])
            coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1,
                                          sinusd1[n1])
            coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1,
                                          sinusd2[n1])
            sinusNodeId.append(nodeIdentifier)
            nodeIdentifier += 1

        arcNodeId = []
        for n1 in range(elementsCountAround):
            node = nodes.createNode(nodeIdentifier, nodetemplateLinearS2S3)
            cache.setNode(node)
            coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1,
                                          arcx[n1])
            coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1,
                                          arcd1[n1])
            arcNodeId.append(nodeIdentifier)
            nodeIdentifier += 1

        noduleNodeId = [[], []]
        for n3 in range(2):
            for n1 in range(3):
                node = nodes.createNode(nodeIdentifier, nodetemplate)
                cache.setNode(node)
                coordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_VALUE, 1,
                                              nodulex[n3][n1])
                coordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_D_DS1, 1,
                                              noduled1[n3][n1])
                coordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_D_DS2, 1,
                                              noduled2[n3][n1])
                coordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_D_DS3, 1,
                                              noduled3[n3][n1])
                noduleNodeId[n3].append(nodeIdentifier)
                nodeIdentifier += 1

        upperNodeId = [[], []]
        for n3 in range(2):
            for n1 in range(len(upperx[n3])):
                node = nodes.createNode(nodeIdentifier, nodetemplateLinearS3)
                cache.setNode(node)
                coordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_VALUE, 1,
                                              upperx[n3][n1])
                coordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_D_DS1, 1,
                                              upperd1[n3][n1])
                coordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_D_DS2, 1,
                                              upperd2[n3][n1])
                upperNodeId[n3].append(nodeIdentifier)
                nodeIdentifier += 1

        #################
        # Create elements
        #################

        mesh = fm.findMeshByDimension(3)

        allMeshGroups = [allGroup.getMeshGroup(mesh) for allGroup in allGroups]
        cuspMeshGroups = [
            cuspGroup.getMeshGroup(mesh) for cuspGroup in cuspGroups
        ]

        linearHermiteLinearBasis = fm.createElementbasis(
            3, Elementbasis.FUNCTION_TYPE_LINEAR_LAGRANGE)
        linearHermiteLinearBasis.setFunctionType(
            2, Elementbasis.FUNCTION_TYPE_CUBIC_HERMITE)

        hermiteLinearLinearBasis = fm.createElementbasis(
            3, Elementbasis.FUNCTION_TYPE_LINEAR_LAGRANGE)
        hermiteLinearLinearBasis.setFunctionType(
            1, Elementbasis.FUNCTION_TYPE_CUBIC_HERMITE)

        bicubichermitelinear = eftfactory_bicubichermitelinear(
            mesh, useCrossDerivatives)
        eftDefault = bicubichermitelinear.createEftNoCrossDerivatives()

        elementIdentifier = max(
            1,
            zinc_utils.getMaximumElementIdentifier(mesh) + 1)

        elementtemplate1 = mesh.createElementtemplate()
        elementtemplate1.setElementShapeType(Element.SHAPE_TYPE_CUBE)

        # wall elements
        for cusp in range(3):
            n1 = cusp * 2 - 1 + nMidCusp
            n2 = n1 * 2
            for e in range(6):
                eft1 = None
                scalefactors = None

                if (e == 0) or (e == 5):
                    # 6 node linear-hermite-linear collapsed wedge element expanding from zero width on outer wall of root, attaching to vertical part of cusp
                    eft1 = mesh.createElementfieldtemplate(
                        linearHermiteLinearBasis)
                    # switch mappings to use DS2 instead of default DS1
                    remapEftNodeValueLabel(eft1, [1, 2, 3, 4, 5, 6, 7, 8],
                                           Node.VALUE_LABEL_D_DS1,
                                           [(Node.VALUE_LABEL_D_DS2, [])])
                    if e == 0:
                        nids = [
                            lowerNodeId[0][n1], arcNodeId[n1],
                            upperNodeId[0][n2], upperNodeId[0][n2 + 1],
                            lowerNodeId[1][n1], upperNodeId[1][n1]
                        ]
                        setEftScaleFactorIds(eft1, [1], [])
                        scalefactors = [-1.0]
                        remapEftNodeValueLabel(eft1, [2],
                                               Node.VALUE_LABEL_D_DS2,
                                               [(Node.VALUE_LABEL_D_DS1, [1])])
                    else:
                        nids = [
                            arcNodeId[n1 + 1], lowerNodeId[0][n1 - 4],
                            upperNodeId[0][n2 + 3], upperNodeId[0][n2 - 8],
                            lowerNodeId[1][n1 - 4], upperNodeId[1][n1 - 4]
                        ]
                        remapEftNodeValueLabel(eft1, [1],
                                               Node.VALUE_LABEL_D_DS2,
                                               [(Node.VALUE_LABEL_D_DS1, [])])
                    ln_map = [1, 2, 3, 4, 5, 5, 6, 6]
                    remapEftLocalNodes(eft1, 6, ln_map)
                elif (e == 1) or (e == 4):
                    # 6 node hermite-linear-linear collapsed wedge element on lower wall
                    eft1 = mesh.createElementfieldtemplate(
                        hermiteLinearLinearBasis)
                    if e == 1:
                        nids = [
                            lowerNodeId[0][n1], lowerNodeId[0][n1 + 1],
                            arcNodeId[n1], sinusNodeId[n1 + 1],
                            lowerNodeId[1][n1], lowerNodeId[1][n1 + 1]
                        ]
                    else:
                        nids = [
                            lowerNodeId[0][n1 + 1], lowerNodeId[0][n1 - 4],
                            sinusNodeId[n1 + 1], arcNodeId[n1 + 1],
                            lowerNodeId[1][n1 + 1], lowerNodeId[1][n1 - 4]
                        ]
                    ln_map = [1, 2, 3, 4, 5, 6, 5, 6]
                    remapEftLocalNodes(eft1, 6, ln_map)
                else:
                    # 8 node elements with wedges on two sides
                    if e == 2:
                        eft1 = bicubichermitelinear.createEftNoCrossDerivatives(
                        )
                        setEftScaleFactorIds(eft1, [1], [])
                        scalefactors = [-1.0]
                        nids = [
                            arcNodeId[n1], sinusNodeId[n1 + 1],
                            upperNodeId[0][n2 + 1], upperNodeId[0][n2 + 2],
                            lowerNodeId[1][n1], lowerNodeId[1][n1 + 1],
                            upperNodeId[1][n1], upperNodeId[1][n1 + 1]
                        ]
                        remapEftNodeValueLabel(eft1, [1],
                                               Node.VALUE_LABEL_D_DS2,
                                               [(Node.VALUE_LABEL_D_DS1, [1])])
                    else:
                        eft1 = eftDefault
                        nids = [
                            sinusNodeId[n1 + 1], arcNodeId[n1 + 1],
                            upperNodeId[0][n2 + 2], upperNodeId[0][n2 + 3],
                            lowerNodeId[1][n1 + 1], lowerNodeId[1][n1 - 4],
                            upperNodeId[1][n1 + 1], upperNodeId[1][n1 - 4]
                        ]
                        remapEftNodeValueLabel(eft1, [2],
                                               Node.VALUE_LABEL_D_DS2,
                                               [(Node.VALUE_LABEL_D_DS1, [])])

                result = elementtemplate1.defineField(coordinates, -1, eft1)
                element = mesh.createElement(elementIdentifier,
                                             elementtemplate1)
                result2 = element.setNodesByIdentifier(eft1, nids)
                if scalefactors:
                    result3 = element.setScaleFactors(eft1, scalefactors)
                else:
                    result3 = 7
                #print('create arterial root wall', cusp, e, 'element',elementIdentifier, result, result2, result3, nids)
                elementIdentifier += 1

                for meshGroup in allMeshGroups:
                    meshGroup.addElement(element)

        # cusps (leaflets)
        for cusp in range(3):
            n1 = cusp * 2 - 1 + nMidCusp
            n2 = n1 * 2
            meshGroups = allMeshGroups + [cuspMeshGroups[cusp]]
            for e in range(2):
                eft1 = bicubichermitelinear.createEftNoCrossDerivatives()
                setEftScaleFactorIds(eft1, [1], [])
                scalefactors = [-1.0]

                if e == 0:
                    nids = [
                        lowerNodeId[0][n1], lowerNodeId[0][n1 + 1],
                        upperNodeId[0][n2], noduleNodeId[0][cusp],
                        arcNodeId[n1], sinusNodeId[n1 + 1],
                        upperNodeId[0][n2 + 1], noduleNodeId[1][cusp]
                    ]
                    remapEftNodeValueLabel(eft1, [4, 8],
                                           Node.VALUE_LABEL_D_DS1,
                                           [(Node.VALUE_LABEL_D_DS1, [1])])
                    remapEftNodeValueLabel(eft1, [4, 8],
                                           Node.VALUE_LABEL_D_DS2,
                                           [(Node.VALUE_LABEL_D_DS3, [])])
                    remapEftNodeValueLabel(eft1, [5], Node.VALUE_LABEL_D_DS2,
                                           [(Node.VALUE_LABEL_D_DS1, [1])])
                    remapEftNodeValueLabel(eft1, [6], Node.VALUE_LABEL_D_DS2,
                                           [(Node.VALUE_LABEL_D_DS2, [1])])
                    remapEftNodeValueLabel(eft1, [7], Node.VALUE_LABEL_D_DS1,
                                           [(Node.VALUE_LABEL_D_DS1, [1])])
                else:
                    nids = [
                        lowerNodeId[0][n1 + 1], lowerNodeId[0][n1 - 4],
                        noduleNodeId[0][cusp], upperNodeId[0][n2 - 8],
                        sinusNodeId[n1 + 1], arcNodeId[n1 + 1],
                        noduleNodeId[1][cusp], upperNodeId[0][n2 + 3]
                    ]
                    remapEftNodeValueLabel(eft1, [3, 7],
                                           Node.VALUE_LABEL_D_DS2,
                                           [(Node.VALUE_LABEL_D_DS3, [])])
                    remapEftNodeValueLabel(eft1, [3, 7],
                                           Node.VALUE_LABEL_D_DS1,
                                           [(Node.VALUE_LABEL_D_DS2, [])])
                    remapEftNodeValueLabel(eft1, [4, 8],
                                           Node.VALUE_LABEL_D_DS1,
                                           [(Node.VALUE_LABEL_D_DS1, [1])])
                    remapEftNodeValueLabel(eft1, [5], Node.VALUE_LABEL_D_DS2,
                                           [(Node.VALUE_LABEL_D_DS2, [1])])
                    remapEftNodeValueLabel(eft1, [6], Node.VALUE_LABEL_D_DS2,
                                           [(Node.VALUE_LABEL_D_DS1, [])])

                result = elementtemplate1.defineField(coordinates, -1, eft1)
                element = mesh.createElement(elementIdentifier,
                                             elementtemplate1)
                result2 = element.setNodesByIdentifier(eft1, nids)
                if scalefactors:
                    result3 = element.setScaleFactors(eft1, scalefactors)
                else:
                    result3 = 7
                #print('create semilunar cusp', cusp, e, 'element',elementIdentifier, result, result2, result3, nids)
                elementIdentifier += 1

                for meshGroup in meshGroups:
                    meshGroup.addElement(element)

        # create annotation points

        datapoint = fiducialPoints.createNode(-1, datapointTemplateExternal)
        cache.setNode(datapoint)
        fiducialCoordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_VALUE, 1,
                                              noduleCentre)
        fiducialLabel.assignString(
            cache, 'aortic valve ctr'
            if aorticNotPulmonary else 'pulmonary valve ctr')

        fm.endChange()
        return annotationGroups
Esempio n. 4
0
 def createHermiteCurvePoints(self,
                              aProportion1,
                              aProportion2,
                              bProportion1,
                              bProportion2,
                              elementsCount,
                              derivativeStart=None,
                              derivativeEnd=None,
                              curveMode=HermiteCurveMode.SMOOTH):
     '''
     Create hermite curve points between two points a and b on the surface, each defined
     by their proportions over the surface in directions 1 and 2.
     Also returns cross direction 2 in plane of surface with similar magnitude to curve derivative 1,
     and unit surface normals.
     :param derivativeStart, derivativeEnd: Optional derivative vectors in 3-D world coordinates
     to match at the start and end of the curves. If omitted, fits in with other derivative or is
     in a straight line from a to b.
     :param elementsCount:  Number of elements out.
     :return: nx[], nd1[], nd2[], nd3[], nProportions[]
     '''
     #print('createHermiteCurvePoints', aProportion1, aProportion2, bProportion1, bProportion2, elementsCount, derivativeStart, derivativeEnd)
     if derivativeStart:
         position = self.createPositionProportion(aProportion1,
                                                  aProportion2)
         _, sd1, sd2 = self.evaluateCoordinates(position, derivatives=True)
         delta_xi1, delta_xi2 = calculate_surface_delta_xi(
             sd1, sd2, derivativeStart)
         dp1Start = delta_xi1 / self.elementsCount1
         if self.loop1:
             dp1Start *= 2.0
         dp2Start = delta_xi2 / self.elementsCount2
         derivativeMagnitudeStart = math.sqrt(dp1Start * dp1Start +
                                              dp2Start * dp2Start)
         dp1Start *= elementsCount
         dp2Start *= elementsCount
         #print('start delta_xi1', delta_xi1, 'delta_xi2', delta_xi2)
         #print('dp1Start', dp1Start, 'dp2Start', dp2Start)
     if derivativeEnd:
         position = self.createPositionProportion(bProportion1,
                                                  bProportion2)
         _, sd1, sd2 = self.evaluateCoordinates(position, derivatives=True)
         delta_xi1, delta_xi2 = calculate_surface_delta_xi(
             sd1, sd2, derivativeEnd)
         dp1End = delta_xi1 / self.elementsCount1
         dp2End = delta_xi2 / self.elementsCount2
         derivativeMagnitudeEnd = math.sqrt(dp1End * dp1End +
                                            dp2End * dp2End)
         dp1End *= elementsCount
         if self.loop1:
             dp1End *= 2.0
         dp2End *= elementsCount
         #print('end delta_xi1', delta_xi1, 'delta_xi2', delta_xi2)
         #print('dp1End', dp1End, 'dp2End', dp2End)
     if not derivativeStart:
         if derivativeEnd:
             dp1Start, dp2Start = interp.interpolateLagrangeHermiteDerivative(
                 [aProportion1, aProportion2], [bProportion1, bProportion2],
                 [dp1End, dp2End], 0.0)
         else:
             dp1Start = bProportion1 - aProportion1
             dp2Start = bProportion2 - aProportion2
         derivativeMagnitudeStart = math.sqrt(
             dp1Start * dp1Start + dp2Start * dp2Start) / elementsCount
     if not derivativeEnd:
         if derivativeStart:
             dp1End, dp2End = interp.interpolateHermiteLagrangeDerivative(
                 [aProportion1, aProportion2], [dp1Start, dp2Start],
                 [bProportion1, bProportion2], 1.0)
         else:
             dp1End = bProportion1 - aProportion1
             dp2End = bProportion2 - aProportion2
         derivativeMagnitudeEnd = math.sqrt(dp1End * dp1End +
                                            dp2End * dp2End) / elementsCount
     maxProportion1 = 2.0 if self.loop1 else 1.0
     #print('derivativeMagnitudeStart', derivativeMagnitudeStart, 'derivativeMagnitudeEnd', derivativeMagnitudeEnd)
     proportions, dproportions = interp.sampleCubicHermiteCurvesSmooth([ [ aProportion1, aProportion2 ], [ bProportion1, bProportion2 ] ], \
         [ [ dp1Start, dp2Start ], [ dp1End, dp2End ] ], elementsCount, derivativeMagnitudeStart, derivativeMagnitudeEnd)[0:2]
     if curveMode != self.HermiteCurveMode.SMOOTH:
         if derivativeStart and (curveMode in [
                 self.HermiteCurveMode.TRANSITION_START,
                 self.HermiteCurveMode.TRANSITION_START_AND_END
         ]):
             addLengthStart = 0.5 * derivativeMagnitudeStart
             lengthFractionStart = 0.5
         else:
             addLengthStart = 0.0
             lengthFractionStart = 1.0
         if derivativeEnd and (curveMode in [
                 self.HermiteCurveMode.TRANSITION_END,
                 self.HermiteCurveMode.TRANSITION_START_AND_END
         ]):
             addLengthEnd = 0.5 * derivativeMagnitudeEnd
             lengthFractionEnd = 0.5
         else:
             addLengthEnd = 0.0
             lengthFractionEnd = 1.0
         proportions, dproportions = interp.sampleCubicHermiteCurves(
             proportions, dproportions, elementsCount, addLengthStart,
             addLengthEnd, lengthFractionStart, lengthFractionEnd)[0:2]
     #print(' proportions', proportions)
     #print('dproportions', dproportions)
     nx = []
     nd1 = []
     nd2 = []
     nd3 = []
     for n in range(0, elementsCount + 1):
         position = self.createPositionProportion(proportions[n][0],
                                                  proportions[n][1])
         x, sd1, sd2 = self.evaluateCoordinates(position, derivatives=True)
         f1 = dproportions[n][0] * self.elementsCount1 / maxProportion1
         f2 = dproportions[n][1] * self.elementsCount2
         d1 = [(f1 * sd1[c] + f2 * sd2[c]) for c in range(3)]
         d3 = vector.crossproduct3(sd1, sd2)
         # handle zero magnitude of d3
         mag = math.sqrt(sum(d3[c] * d3[c] for c in range(3)))
         if mag > 0.0:
             d3 = [(d3[c] / mag) for c in range(3)]
         d2 = vector.crossproduct3(d3, d1)
         nx.append(x)
         nd2.append(d2)
         nd1.append(d1)
         nd3.append(d3)
     #print('createHermiteCurvePoints end \n nx', nx,'\nnd1',nd1,'\nnd2',nd2,'\nnd3',nd3)
     return nx, nd1, nd2, nd3, proportions
    def getPoints(cls, options):
        """
        Get point coordinates and derivatives for the arterial valve ring.
        Optional extra parameters allow origin and orientation to be set.
        :param options: Dict containing options. See getDefaultOptions().
        :return: x, d1, d2, d3 all indexed by [n3=wall][n2=inlet->outlet][n1=around] where
        d1 is around, d2 is in direction inlet->outlet.
        d3 is radial and undefined at n2 == 1.
         """
        unitScale = options['Unit scale']
        innerRadius = unitScale * 0.5 * options['Inner diameter']
        innerRadialDisplacement = unitScale * options[
            'Inner radial displacement']
        innerSinusRadialDisplacement = unitScale * options[
            'Inner sinus radial displacement']
        outerAngleRadians = math.radians(options['Outer angle degrees'])
        outerHeight = unitScale * options['Outer height']
        outerRadialDisplacement = unitScale * options[
            'Outer radial displacement']
        outerSinusRadialDisplacement = unitScale * options[
            'Outer sinus radial displacement']
        outletLength = unitScale * options['Outlet length']
        sinusAngleRadians = math.radians(options['Sinus angle degrees'])
        sinusDepth = unitScale * options['Sinus depth']
        wallThickness = unitScale * options['Wall thickness']
        rotationAzimuthRadians = math.radians(
            options['Rotation azimuth degrees'])
        rotationElevationRadians = math.radians(
            options['Rotation elevation degrees'])
        rotationRollRadians = math.radians(options['Rotation roll degrees'])
        centre = [
            unitScale * options['Translation x'],
            unitScale * options['Translation y'],
            unitScale * options['Translation z']
        ]
        outerRadius = innerRadius + wallThickness
        innerInletRadius = innerRadius + innerRadialDisplacement
        innerInletSinusRadius = innerRadius + innerSinusRadialDisplacement

        elementsCountAround = 6  # fixed
        radiansPerElementAround = 2.0 * math.pi / elementsCountAround
        pi_3 = radiansPerElementAround

        #centre = [ 0.0, 0.0, 0.0 ]
        #axis1 = [ 1.0, 0.0, 0.0 ]
        #axis2 = [ 0.0, 1.0, 0.0 ]
        #axis3 = vector.crossproduct3(axis1, axis2)
        axis1, axis2, axis3 = eulerToRotationMatrix3([
            rotationAzimuthRadians, rotationElevationRadians,
            rotationRollRadians
        ])

        x = [[None, None], [None, None]]
        d1 = [[None, None], [None, None]]
        d2 = [[None, None], [None, None]]
        d3 = [[None, None], [None, None]]

        # inlet
        # inner layer, with sinuses
        outletz = outerHeight
        inletz = outletz - outletLength
        sinusz = -sinusDepth
        outletCentre = [(centre[c] + outletz * axis3[c]) for c in range(3)]
        inletCentre = [(centre[c] + inletz * axis3[c]) for c in range(3)]
        sinusCentre = [(centre[c] + sinusz * axis3[c]) for c in range(3)]
        # calculate magnitude of d1, d2 at inner sinus
        leafd1mag = innerInletRadius * radiansPerElementAround  # was 0.5*
        leafd2r, leafd2z = interpolateLagrangeHermiteDerivative(
            [innerRadialDisplacement, 0.0], [0.0, outletLength],
            [0.0, outletLength], 0.0)
        sinusd1mag = innerInletSinusRadius * radiansPerElementAround  # initial value only
        sinusd1mag = vector.magnitude(
            smoothCubicHermiteDerivativesLine(
                [[innerInletRadius, 0.0, inletz],
                 [
                     innerInletSinusRadius * math.cos(pi_3),
                     innerInletSinusRadius * math.sin(pi_3), sinusz
                 ]], [[0.0, leafd1mag, 0.0],
                      [
                          -sinusd1mag * math.sin(pi_3),
                          sinusd1mag * math.cos(pi_3), 0.0
                      ]],
                fixStartDerivative=True,
                fixEndDirection=True)[1])
        sinusd2r, sinusd2z = smoothCubicHermiteDerivativesLine(
            [[innerInletSinusRadius, -sinusDepth],
             [innerInletRadius, outerHeight]], [[
                 outletLength * math.sin(sinusAngleRadians),
                 outletLength * math.cos(sinusAngleRadians)
             ], [0.0, outletLength]],
            fixStartDirection=True,
            fixEndDerivative=True)[0]
        magd3 = wallThickness + outerRadialDisplacement - innerRadialDisplacement
        x[0][0] = []
        d1[0][0] = []
        d2[0][0] = []
        d3[0][0] = []
        for n1 in range(elementsCountAround):
            radiansAround = n1 * radiansPerElementAround
            cosRadiansAround = math.cos(radiansAround)
            sinRadiansAround = math.sin(radiansAround)
            if (n1 % 2) == 0:
                # leaflet junction
                cx = inletCentre
                r = innerInletRadius
                d1mag = leafd1mag
                d2mag1 = leafd2r * cosRadiansAround
                d2mag2 = leafd2r * sinRadiansAround
                d2mag3 = leafd2z
            else:
                # sinus / leaflet centre
                cx = sinusCentre
                r = innerInletSinusRadius
                d1mag = sinusd1mag
                d2mag1 = sinusd2r * cosRadiansAround
                d2mag2 = sinusd2r * sinRadiansAround
                d2mag3 = sinusd2z
            d3mag1 = magd3 * cosRadiansAround
            d3mag2 = magd3 * sinRadiansAround
            d3mag3 = 0.0
            x[0][0].append([
                (cx[c] + r *
                 (cosRadiansAround * axis1[c] + sinRadiansAround * axis2[c]))
                for c in range(3)
            ])
            d1[0][0].append([
                d1mag *
                (-sinRadiansAround * axis1[c] + cosRadiansAround * axis2[c])
                for c in range(3)
            ])
            d2[0][0].append([
                (d2mag1 * axis1[c] + d2mag2 * axis2[c] + d2mag3 * axis3[c])
                for c in range(3)
            ])
            d3[0][0].append([
                (d3mag1 * axis1[c] + d3mag2 * axis2[c] + d3mag3 * axis3[c])
                for c in range(3)
            ])
        # outer layer
        extRadius = outerRadius + outerRadialDisplacement
        leafd2r, leafd2z = smoothCubicHermiteDerivativesLine(
            [[extRadius, 0.0], [outerRadius, outerHeight]], [[
                -outerHeight * math.sin(outerAngleRadians),
                outerHeight * math.cos(outerAngleRadians)
            ], [0.0, outletLength]],
            fixStartDirection=True,
            fixEndDerivative=True)[0]
        # calculate magnitude of d1, d2 at outer sinus
        extSinusRadius = outerRadius + outerSinusRadialDisplacement
        leafd1mag = extRadius * radiansPerElementAround
        sinusd1mag = extSinusRadius * radiansPerElementAround  # initial value only
        sinusd1mag = vector.magnitude(
            smoothCubicHermiteDerivativesLine(
                [[extRadius, 0.0, 0.0],
                 [
                     extSinusRadius * math.cos(pi_3),
                     extSinusRadius * math.sin(pi_3), 0.0
                 ]], [[0.0, leafd1mag, 0.0],
                      [
                          -sinusd1mag * math.sin(pi_3),
                          sinusd1mag * math.cos(pi_3), 0.0
                      ]],
                fixStartDerivative=True,
                fixEndDirection=True)[1])
        sinusd2r, sinusd2z = smoothCubicHermiteDerivativesLine(
            [[extSinusRadius, 0.0], [outerRadius, outerHeight]], [[
                -outerHeight * math.sin(outerAngleRadians),
                outerHeight * math.cos(outerAngleRadians)
            ], [0.0, outletLength]],
            fixStartDirection=True,
            fixEndDerivative=True)[0]
        centre = centre
        x[1][0] = []
        d1[1][0] = []
        d2[1][0] = []
        d3[1][0] = []
        for n1 in range(elementsCountAround):
            radiansAround = n1 * radiansPerElementAround
            cosRadiansAround = math.cos(radiansAround)
            sinRadiansAround = math.sin(radiansAround)
            if (n1 % 2) == 0:
                # leaflet junction
                cx = inletCentre
                r = extRadius
                d1mag = leafd1mag
                d2mag1 = leafd2r * cosRadiansAround
                d2mag2 = leafd2r * sinRadiansAround
                d2mag3 = leafd2z
            else:
                # sinus / leaflet centre
                cx = sinusCentre
                r = extSinusRadius
                d1mag = sinusd1mag
                d2mag1 = sinusd2r * cosRadiansAround
                d2mag2 = sinusd2r * sinRadiansAround
                d2mag3 = sinusd2z
            d3mag1 = magd3 * cosRadiansAround
            d3mag2 = magd3 * sinRadiansAround
            d3mag3 = 0.0
            x[1][0].append([
                (centre[c] + r *
                 (cosRadiansAround * axis1[c] + sinRadiansAround * axis2[c]))
                for c in range(3)
            ])
            d1[1][0].append([
                d1mag *
                (-sinRadiansAround * axis1[c] + cosRadiansAround * axis2[c])
                for c in range(3)
            ])
            d2[1][0].append([
                (d2mag1 * axis1[c] + d2mag2 * axis2[c] + d2mag3 * axis3[c])
                for c in range(3)
            ])
            d3[1][0].append([
                (d3mag1 * axis1[c] + d3mag2 * axis2[c] + d3mag3 * axis3[c])
                for c in range(3)
            ])

        # outlet
        x[0][1], d1[0][1] = createCirclePoints(
            outletCentre, [axis1[c] * innerRadius for c in range(3)],
            [axis2[c] * innerRadius for c in range(3)], elementsCountAround)
        x[1][1], d1[1][1] = createCirclePoints(
            outletCentre, [axis1[c] * outerRadius for c in range(3)],
            [axis2[c] * outerRadius for c in range(3)], elementsCountAround)
        d2[1][1] = d2[0][1] = [[axis3[c] * outletLength
                                for c in range(3)]] * elementsCountAround
        d3[1][1] = d3[0][1] = None

        return x, d1, d2, d3