Пример #1
0
 /// <summary>
 /// Writes members variables of a GOM class to 'SB'.
 /// </summary>
 /// <param name="SB">Where the comment goes.</param>
 /// <param name="S">Used for basis vector names and output language.</param>
 /// <param name="cgd">Intermediate data for code generation. Also contains plugins and cog.</param>
 /// <param name="FT">Float point type of 'GOM'.</param>
 /// <param name="gom">The general outermorphism for which the class should be written.</param>
 public static void WriteMemberVariables(StringBuilder SB, Specification S, G25.CG.Shared.CGdata cgd, FloatType FT, G25.GOM gom)
 {
     SB.AppendLine("public:");
     for (int g = 1; g < gom.Domain.Length; g++) // start at '1' in order to skip scalar grade
     {
         SB.AppendLine("\t/// Matrix for grade " + g + "; the size is " + gom.DomainForGrade(g).Length + " x " + gom.RangeForGrade(g).Length);
         SB.AppendLine("\t" + FT.type + " m_m" + g + "[" +
                       gom.DomainForGrade(g).Length *gom.RangeForGrade(g).Length + "];");
     }
 }
Пример #2
0
        /// <summary>
        /// Writes comments of a GOM class to 'SB'.
        /// </summary>
        /// <param name="SB">Where the comment goes.</param>
        /// <param name="S">Used for basis vector names and output language.</param>
        /// <param name="cgd">Intermediate data for code generation. Also contains plugins and cog.</param>
        /// <param name="FT">Float point type of 'GOM'.</param>
        /// <param name="gom">The general outermorphism for which the class should be written.</param>
        public static void WriteComment(StringBuilder SB, Specification S, G25.CG.Shared.CGdata cgd, FloatType FT, G25.GOM gom)
        {
            SB.AppendLine("/**");
            SB.AppendLine(" * This class can hold a general outermorphism.");
            SB.AppendLine(" * ");

            SB.AppendLine(" * The coordinates are stored in type " + FT.type + ".");
            SB.AppendLine(" * ");

            SB.AppendLine(" * There are " + gom.Domain.Length + " matrices, one for each grade.");
            SB.AppendLine(" * The columns of these matrices are the range of the outermorphism.");
            SB.AppendLine(" * Matrices are stored in row-major order. So the coordinates of rows are stored contiguously.");
            for (int g = 1; g < gom.Domain.Length; g++) // start at '1' in order to skip scalar grade
            {
                SB.Append(" * Domain grade " + g + ": ");
                for (int i = 0; i < gom.DomainForGrade(g).Length; i++)
                {
                    if (i > 0)
                    {
                        SB.Append(", ");
                    }
                    SB.Append(gom.DomainForGrade(g)[i].ToString(S.m_basisVectorNames));
                }

                SB.AppendLine(".");
            }
            SB.AppendLine(" * ");
            if (!gom.DomainAndRangeAreEqual())
            {
                for (int g = 1; g < gom.Range.Length; g++) // start at '1' in order to skip scalar grade
                {
                    SB.Append(" * Range grade " + g + ": ");
                    for (int i = 0; i < gom.RangeForGrade(g).Length; i++)
                    {
                        if (i > 0)
                        {
                            SB.Append(", ");
                        }
                        SB.Append(gom.RangeForGrade(g)[i].ToString(S.m_basisVectorNames));
                    }

                    SB.AppendLine(".");
                }
            }
            else
            {
                SB.AppendLine(" * The range and domain are equal.");
            }
            SB.AppendLine(" * ");

            SB.AppendLine(" */");
        }
Пример #3
0
        /// <summary>
        /// Writes members variables of a GOM class to 'SB'.
        /// </summary>
        /// <param name="SB">Where the comment goes.</param>
        /// <param name="S">Used for basis vector names and output language.</param>
        /// <param name="cgd">Intermediate data for code generation. Also contains plugins and cog.</param>
        /// <param name="FT">Float point type of 'GOM'.</param>
        /// <param name="gom">The general outermorphism for which the class should be written.</param>
        public static void WriteMemberVariables(StringBuilder SB, Specification S, G25.CG.Shared.CGdata cgd, FloatType FT, G25.GOM gom)
        {
            int nbTabs = 1;

            for (int g = 1; g < gom.Domain.Length; g++) // start at '1' in order to skip scalar grade
            {
                string comment = "Matrix for grade " + g + "; the size is " + gom.DomainForGrade(g).Length + " x " + gom.RangeForGrade(g).Length;
                new G25.CG.Shared.Comment(comment).Write(SB, S, nbTabs);

                SB.AppendLine(new string('\t', nbTabs) + Keywords.PackageProtectedAccessModifier(S) + " " + FT.type + "[] m_m" + g + " = new " +
                              FT.type + "[" + gom.DomainForGrade(g).Length *gom.RangeForGrade(g).Length + "];");
            }
        }
Пример #4
0
        /// <summary>
        /// Writes 'set()' declarations of a GOM class to 'SB'.
        /// </summary>
        /// <param name="SB">Where the code goes.</param>
        /// <param name="S">Used for basis vector names and output language.</param>
        /// <param name="cgd">Intermediate data for code generation. Also contains plugins and cog.</param>
        /// <param name="FT">Float point type of 'GOM'.</param>
        /// <param name="gom">The general outermorphism for which the class should be written.</param>
        /// <param name="className">Mangled name of GOM class.</param>
        /// <param name="rangeVectorSMVname">The name of the SMV which can represent a column of the OM.</param>
        public static void WriteSetDeclarations(StringBuilder SB, Specification S, G25.CG.Shared.CGdata cgd, FloatType FT, G25.GOM gom, string className, string rangeVectorSMVname)
        {
            cgd.m_cog.EmitTemplate(SB, "GOMsetDecl", "S=", S, "FT=", FT, "className=", className, "rangeVectorSMVname=", rangeVectorSMVname);

            {                                // extra code for per-grade-per-basisblade functions to set OM from vectors
                SB.AppendLine("\tprivate:");
                bool     matrixMode = false; // this value is irrelevant at this point
                string   typeName   = FT.GetMangledName(S, gom.Name);
                string   prefix     = typeName + "::";
                string[] funcNames  = G25.CG.Shared.OMinit.GetSetFromLowerGradeFunctionNames(S, FT, matrixMode);
                for (int g = 1; g < gom.Domain.Length; g++)
                {
                    for (int d = 0; d < gom.DomainForGrade(g).Length; d++)
                    {
                        string funcName = funcNames[g] + "_" + d;
                        if (funcName.IndexOf(prefix) == 0)
                        {
                            funcName = funcName.Substring(prefix.Length);
                        }
                        SB.AppendLine("\tvoid " + funcName + "();");
                    }
                }
                SB.AppendLine("\tpublic:");
            }
        }
Пример #5
0
        /// <summary>
        /// Writes a function to set a GOM struct according to vector images, for all floating point types.
        /// </summary>
        /// <param name="S">Used for basis vector names and output language.</param>
        /// <param name="cgd">Results go here. Also intermediate data for code generation. Also contains plugins and cog.</param>
        /// <param name="FT">Float type.</param>
        /// <param name="matrixMode">When true, generates code for setting from matrix instead of vector images.</param>
        /// <param name="transpose">When this parameter is true and <c>matrixMode</c> is true, generates code for setting from transpose matrix.</param>
        public static void WriteSetVectorImages(Specification S, G25.CG.Shared.CGdata cgd, FloatType FT, bool matrixMode, bool transpose)
        {
            G25.GOM gom = S.m_GOM;

            // get the 'plan' on how to initialize all domain basis blades efficiently:
            uint[][][] plan  = G25.CG.Shared.OMinit.ComputeOmInitFromVectorsPlan(S, gom);
            double[][] signs = G25.CG.Shared.OMinit.ComputeOmInitFromVectorsSigns(S, gom, plan);

            // get range vector type
            G25.SMV rangeVectorType = G25.CG.Shared.OMinit.GetRangeVectorType(S, FT, cgd, gom);

            // setup array of arguments, function specification, etc
            int NB_ARGS = (matrixMode) ? 1 : gom.DomainVectors.Length;

            string[]            argTypes         = new string[NB_ARGS], argNames = new string[NB_ARGS];
            RefGA.Multivector[] symbolicBBvalues = new RefGA.Multivector[1 << S.m_dimension]; // symbolic basis blade values go here
            if (matrixMode)
            {
                argTypes[0] = FT.type;
                argNames[0] = "M";

                // convert matrix columns to symbolic Multivector values
                for (int d = 0; d < gom.DomainVectors.Length; d++)
                {
                    RefGA.BasisBlade[] IV = new RefGA.BasisBlade[gom.RangeVectors.Length];
                    for (int r = 0; r < gom.RangeVectors.Length; r++)
                    {
                        int    matrixIdx = (transpose) ? (d * gom.RangeVectors.Length + r) : (r * gom.DomainVectors.Length + d);
                        string entryName = argNames[0] + "[" + matrixIdx + "]";
                        IV[r] = new RefGA.BasisBlade(gom.RangeVectors[r].bitmap, 1.0, entryName);
                    }
                    symbolicBBvalues[gom.DomainVectors[d].bitmap] = new RefGA.Multivector(IV);
                }
            }
            else
            {
                for (int d = 0; d < NB_ARGS; d++)
                {
                    argTypes[d] = rangeVectorType.Name;
                    argNames[d] = "i" + gom.DomainVectors[d].ToLangString(S.m_basisVectorNames);
                    bool ptr = S.OutputC();

                    symbolicBBvalues[gom.DomainVectors[d].bitmap] = G25.CG.Shared.Symbolic.SMVtoSymbolicMultivector(S, rangeVectorType, argNames[d], ptr);
                }
            }

            // generate function names for all grades (basis blade names not included)
            string typeName = FT.GetMangledName(S, gom.Name);

            string[] funcNames = GetSetFromLowerGradeFunctionNames(S, FT, matrixMode);


            // setup instructions (for main function, and subfunctions for grades)
            List <G25.CG.Shared.Instruction> mainI = new List <G25.CG.Shared.Instruction>();

            List <G25.CG.Shared.Instruction>[] bladeI = new List <G25.CG.Shared.Instruction> [1 << S.m_dimension];
            {
                bool   mustCast   = false;
                int    nbTabs     = 1;
                string dstName    = (S.OutputC()) ? G25.fgs.RETURN_ARG_NAME : SmvUtil.THIS;
                bool   dstPtr     = S.OutputCppOrC();
                bool   declareDst = false;
                for (int g = 1; g < gom.Domain.Length; g++)
                {
                    for (int d = 0; d < gom.DomainForGrade(g).Length; d++)
                    {
                        G25.SMVOM        smvOM       = gom.DomainSmvForGrade(g)[d];
                        RefGA.BasisBlade domainBlade = gom.DomainForGrade(g)[d];


                        if (g > 1)
                        {
                            bladeI[domainBlade.bitmap] = new List <G25.CG.Shared.Instruction>();

                            string funcCallCode = funcNames[g] + "_" + d + "(";
                            if (S.OutputC())
                            {
                                funcCallCode += G25.fgs.RETURN_ARG_NAME;
                            }
                            funcCallCode += ");";
                            mainI.Add(new G25.CG.Shared.VerbatimCodeInstruction(nbTabs, funcCallCode));
                        }

                        // follow the plan
                        RefGA.Multivector value = new RefGA.Multivector(signs[g][d]);
                        uint[]            P     = plan[g][d];
                        for (int p = 0; p < P.Length; p++)
                        {
                            value = RefGA.Multivector.op(value, symbolicBBvalues[P[p]]);
                        }

                        // add instructions
                        List <G25.CG.Shared.Instruction> I = (g == 1) ? mainI : bladeI[domainBlade.bitmap];
                        I.Add(new G25.CG.Shared.CommentInstruction(nbTabs, "Set image of " + domainBlade.ToString(S.m_basisVectorNames)));
                        I.Add(new G25.CG.Shared.AssignInstruction(nbTabs, smvOM, FT, mustCast, value, dstName, dstPtr, declareDst));

                        // store symbolic value
                        symbolicBBvalues[domainBlade.bitmap] = G25.CG.Shared.Symbolic.SMVtoSymbolicMultivector(S, smvOM, dstName, dstPtr);
                    }
                }
            }

            // output grade > 1 functions
            if (cgd.generateOmInitCode(FT.type))
            {
                for (int g = 2; g < gom.Domain.Length; g++)
                {
                    for (int d = 0; d < gom.DomainForGrade(g).Length; d++)
                    {
                        RefGA.BasisBlade domainBlade = gom.DomainForGrade(g)[d];

                        string  funcName = funcNames[g] + "_" + d;
                        G25.fgs F        = new G25.fgs(funcName, funcName, "", new string[0], new string[0], new string[] { FT.type }, null, null, null); // null, null = metricName, comment, options
                        //F.InitArgumentPtrFromTypeNames(S);

                        bool computeMultivectorValue = false;

                        G25.CG.Shared.FuncArgInfo returnArgument = null;
                        if (S.OutputC())
                        {
                            returnArgument = new G25.CG.Shared.FuncArgInfo(S, F, -1, FT, gom.Name, computeMultivectorValue);
                        }

                        int nbArgs = 0;
                        G25.CG.Shared.FuncArgInfo[] FAI = G25.CG.Shared.FuncArgInfo.GetAllFuncArgInfo(S, F, nbArgs, FT, S.m_GMV.Name, computeMultivectorValue);

                        Comment comment;
                        comment = new Comment("Sets grade " + g + " part of outermorphism matrix based on lower grade parts.");
                        bool inline     = false; // do not inline this potentially huge function
                        bool staticFunc = false;
                        bool writeDecl  = S.OutputC();
                        G25.CG.Shared.Functions.WriteFunction(S, cgd, F, inline, staticFunc, "void", funcName, returnArgument, FAI, bladeI[domainBlade.bitmap], comment, writeDecl);
                    }
                }
            }


            {                                                                                                                            // output grade 1 function
                G25.fgs F = new G25.fgs(funcNames[1], funcNames[1], "", argTypes, argNames, new string[] { FT.type }, null, null, null); // null, null = metricName, comment, options
                F.InitArgumentPtrFromTypeNames(S);
                if (matrixMode)
                {
                    F.m_argumentPtr[0] = S.OutputCppOrC();
                    F.m_argumentArr[0] = S.OutputCSharpOrJava();
                }

                bool computeMultivectorValue = false;

                G25.CG.Shared.FuncArgInfo returnArgument = null;
                if (S.OutputC())
                {
                    returnArgument = new G25.CG.Shared.FuncArgInfo(S, F, -1, FT, gom.Name, computeMultivectorValue);
                }

                G25.CG.Shared.FuncArgInfo[] FAI = G25.CG.Shared.FuncArgInfo.GetAllFuncArgInfo(S, F, NB_ARGS, FT, S.m_GMV.Name, computeMultivectorValue);

                Comment comment;
                if (!matrixMode)
                {
                    comment = new Comment("Sets " + typeName + " from images of the domain vectors.");
                }
                else
                {
                    comment = new Comment("Sets " + typeName + " from a " + (transpose ? "transposed " : "") + "matrix");
                }
                bool inline     = false; // do not inline this potentially huge function
                bool staticFunc = false;
                bool writeDecl  = S.OutputC();
                G25.CG.Shared.Functions.WriteFunction(S, cgd, F, inline, staticFunc, "void", funcNames[1], returnArgument, FAI, mainI, comment, writeDecl);
            }
        } // end of WriteSetVectorImages()
Пример #6
0
        /// <summary>
        /// Generates functions which compute parts of the application of a general outermorphism to a general multivector.
        ///
        /// This function should be called early on in the code generation process, at least
        /// before any of the <c>???()</c> functions is called.
        /// </summary>
        /// <param name="S">Specification (used for output language, GMV).</param>
        /// <param name="cgd">Where the result goes.</param>
        public static void WriteGomParts(Specification S, CGdata cgd)
        {
            if (S.m_GOM == null)
            {
                return;                  // nothing to do if GOM not defiend
            }
            int nbBaseTabs = (S.OutputCSharpOrJava()) ? 1 : 0;
            int nbCodeTabs = nbBaseTabs + 1;

            G25.GMV gmv = S.m_GMV;
            G25.GOM gom = S.m_GOM;

            string nameGOM    = "O";
            string nameSrcGMV = "A";
            string nameDstGMV = "C";

            // get symbolic multivector value
            RefGA.Multivector[] M1 = null;
            {
                bool ptr       = (S.OutputC());
                int  allGroups = -1;
                M1 = G25.CG.Shared.Symbolic.GMVtoSymbolicMultivector(S, gmv, nameSrcGMV, ptr, allGroups);
            }

            foreach (G25.FloatType FT in S.m_floatTypes)
            {
                // map from code fragment to name of function
                Dictionary <string, string> generatedCode = new Dictionary <string, string>();

                // loop over all groups of the GMV, multiply with GOM, and assign the result
                for (int srcGroup = 0; srcGroup < gmv.NbGroups; srcGroup++)
                {
                    RefGA.Multivector inputValue = M1[srcGroup];
                    if (inputValue.IsScalar())
                    {
                        continue;
                    }

                    // Replace each basis blade in 'inputValue' with its value under the outermorphism.
                    RefGA.Multivector returnValue = RefGA.Multivector.ZERO; // returnValue = gom * gmv[srcGroup]
                    for (int i = 0; i < inputValue.BasisBlades.Length; i++)
                    {
                        // get input blade and domain for that grade
                        RefGA.BasisBlade   inputBlade   = inputValue.BasisBlades[i];
                        RefGA.BasisBlade[] domainBlades = gom.DomainForGrade(inputBlade.Grade());
                        for (int c = 0; c < domainBlades.Length; c++)
                        {
                            // if a match is found in the domain, add range vector to m_returnValue
                            if (domainBlades[c].bitmap == inputBlade.bitmap)
                            {
                                bool ptr = (S.OutputC());
                                RefGA.Multivector omColumnValue = G25.CG.Shared.Symbolic.SMVtoSymbolicMultivector(S, gom.DomainSmvForGrade(inputBlade.Grade())[c], nameGOM, ptr);
                                RefGA.Multivector inputBladeScalarMultiplier  = new RefGA.Multivector(new RefGA.BasisBlade(inputBlade, 0));
                                RefGA.Multivector domainBladeScalarMultiplier = new RefGA.Multivector(new RefGA.BasisBlade(domainBlades[c], 0));
                                returnValue = RefGA.Multivector.Add(returnValue,
                                                                    RefGA.Multivector.gp(
                                                                        RefGA.Multivector.gp(omColumnValue, inputBladeScalarMultiplier),
                                                                        domainBladeScalarMultiplier));
                                break; // no need to search the other domainBlades too
                            }
                        }
                    } // end of 'compute return value'

                    // assign returnValue to various groups of the gmv
                    for (int dstGroup = 0; dstGroup < gmv.NbGroups; dstGroup++)
                    {
                        bool   mustCast   = false;
                        bool   writeZeros = false; // no need to generate "+= 0.0;"
                        int    dstBaseIdx = 0;
                        string code       = G25.CG.Shared.CodeUtil.GenerateGMVassignmentCode(S, FT, mustCast, gmv, nameDstGMV, dstGroup, dstBaseIdx, returnValue, nbCodeTabs, writeZeros);

                        string funcName = GetGomPartFunctionName(S, FT, srcGroup, dstGroup);
                        cgd.m_gmvGomPartFuncNames[new Tuple <string, string>(FT.type, funcName)] = (code.Length > 0);

                        if (code.Length == 0)
                        {
                            continue;
                        }

                        if (!S.m_GMV.IsGroupedByGrade(S.m_dimension))
                        {
                            code = code.Replace("=", "+=");
                        }

                        // check if code was already generated, and, if so, reuse it
                        if (generatedCode.ContainsKey(code))
                        {
                            // ready generated: call that function
                            code = "\t" + generatedCode[code] + "(" + nameGOM + ", " + nameSrcGMV + ", " + nameDstGMV + ");\n";
                        }
                        else
                        {
                            // not generated yet: remember code -> function
                            generatedCode[code] = funcName;
                        }

                        // write comment
                        string comment = "Computes the partial application of a general outermorphism to a general multivector";

                        string OM_PTR = "";
                        if (S.OutputC())
                        {
                            OM_PTR = "*";
                        }
                        else if (S.OutputCpp())
                        {
                            OM_PTR = "&";
                        }

                        string ACCESS = "";
                        if (S.OutputJava())
                        {
                            ACCESS = "protected static ";
                        }
                        else if (S.OutputCSharp())
                        {
                            ACCESS = "protected internal static ";
                        }

                        string ARR   = (S.OutputCSharpOrJava()) ? "[] " : " *";
                        string CONST = (S.OutputCSharpOrJava()) ? "" : "const ";

                        string funcDecl = ACCESS + "void " + funcName + "(" + CONST + FT.GetMangledName(S, gom.Name) + " " + OM_PTR + nameGOM + ", " + CONST + FT.type + ARR + nameSrcGMV + ", " + FT.type + ARR + nameDstGMV + ")";

                        if (S.OutputCppOrC())
                        {
                            new Comment(comment).Write(cgd.m_declSB, S, nbBaseTabs);
                            cgd.m_declSB.Append(funcDecl); cgd.m_declSB.AppendLine(";");
                        }
                        else
                        {
                            new Comment(comment).Write(cgd.m_defSB, S, nbBaseTabs);
                        }

                        // emit def
                        cgd.m_defSB.Append('\t', nbBaseTabs);
                        cgd.m_defSB.Append(funcDecl);
                        cgd.m_defSB.AppendLine(" {");
                        cgd.m_defSB.Append(code);
                        cgd.m_defSB.Append('\t', nbBaseTabs);
                        cgd.m_defSB.AppendLine("}");
                    } // end of loop over all dest GMV groups
                }     // end of loop over all source GMV groups
            }         // end of loop over all float types
        }             // end of function WriteGomParts()
Пример #7
0
        /// <summary>
        /// Writes the definition of an GOM struct to 'SB' (including comments).
        /// </summary>
        /// <param name="SB">Where the code goes.</param>
        /// <param name="S">Used for basis vector names and output language.</param>
        /// <param name="cgd">Intermediate data for code generation. Also contains plugins and cog.</param>
        /// <param name="FT">Float point type of 'SMV'.</param>
        /// <param name="gom">The general outermorphism for which the struct should be written.</param>
        public static void WriteGOMstruct(StringBuilder SB, Specification S, G25.CG.Shared.CGdata cgd, FloatType FT, G25.GOM gom)
        {
            SB.AppendLine("");

            { // comments for type:
                SB.AppendLine("/**");
                SB.AppendLine(" * This struct can hold a general outermorphism.");
                SB.AppendLine(" * ");

                SB.AppendLine(" * The coordinates are stored in type " + FT.type + ".");
                SB.AppendLine(" * ");

                SB.AppendLine(" * There are " + gom.Domain.Length + " matrices, one for each grade.");
                SB.AppendLine(" * The columns of these matrices are the range of the outermorphism.");
                SB.AppendLine(" * Matrices are stored in row-major order. So the coordinates of rows are stored contiguously.");
                for (int g = 1; g < gom.Domain.Length; g++) // start at '1' in order to skip scalar grade
                {
                    SB.Append(" * Domain grade " + g + ": ");
                    for (int i = 0; i < gom.DomainForGrade(g).Length; i++)
                    {
                        if (i > 0)
                        {
                            SB.Append(", ");
                        }
                        SB.Append(gom.DomainForGrade(g)[i].ToString(S.m_basisVectorNames));
                    }

                    SB.AppendLine(".");
                }
                SB.AppendLine(" * ");
                if (!gom.DomainAndRangeAreEqual())
                {
                    for (int g = 1; g < gom.Range.Length; g++) // start at '1' in order to skip scalar grade
                    {
                        SB.Append(" * Range grade " + g + ": ");
                        for (int i = 0; i < gom.RangeForGrade(g).Length; i++)
                        {
                            if (i > 0)
                            {
                                SB.Append(", ");
                            }
                            SB.Append(gom.RangeForGrade(g)[i].ToString(S.m_basisVectorNames));
                        }

                        SB.AppendLine(".");
                    }
                }
                else
                {
                    SB.AppendLine(" * The range and domain are equal.");
                }
                SB.AppendLine(" * ");

                SB.AppendLine(" */");
            } // end of comment

            // typedef
            SB.AppendLine("typedef struct ");
            SB.AppendLine("{");
            for (int g = 1; g < gom.Domain.Length; g++) // start at '1' in order to skip scalar grade
            {
                SB.AppendLine("\t/** Matrix for grade " + g + "; the size is " + gom.DomainForGrade(g).Length + " x " + gom.RangeForGrade(g).Length + " */");
                SB.AppendLine("\t" + FT.type + " m" + g + "[" +
                              gom.DomainForGrade(g).Length *gom.RangeForGrade(g).Length + "];");
            }

            SB.AppendLine("} " + FT.GetMangledName(S, gom.Name) + ";");
        }