Exemplo n.º 1
0
        /// <summary>
        /// Generate counter example for reachability and deadlock
        ///  [ REFS: '', DEREFS: automataBDD.transitionBDD, automataBDD.priorityTransitionsBDD, this.traces ]
        /// </summary>
        /// <param name="automataBDD"></param>
        /// <param name="encoder"></param>
        /// <returns></returns>
        public void GetMCResult(AutomataBDD automataBDD, BDDEncoder encoder)
        {
            VerificationOutput.CounterExampleTrace.Add(InitialStep);

            if (this.traces.Count > 0)
            {
                ExpressionBDDEncoding initEncoding = automataBDD.initExpression.TranslateBoolExpToBDD(encoder.model);
                this.traces.Insert(0, CUDD.Function.Or(initEncoding.GuardDDs));

                Valuation currentValuation = this.InitialStep.GlobalEnv;
                Valuation lastValuation;

                List <CUDDNode> allTransitions = new List <CUDDNode>();
                allTransitions.AddRange(automataBDD.transitionBDD);
                allTransitions.AddRange(automataBDD.Ticks);

                for (int i = 1; i < this.traces.Count; i++)
                {
                    //Get event information
                    CUDD.Ref(allTransitions);
                    CUDD.Ref(this.traces[i], this.traces[i - 1]);
                    CUDDNode transitionTemp     = CUDD.Function.And(this.traces[i - 1], encoder.model.SwapRowColVars(this.traces[i]));
                    CUDDNode transWithEventInfo = CUDD.Function.And(transitionTemp, allTransitions);

                    transWithEventInfo = CUDD.Abstract.ThereExists(transWithEventInfo, encoder.model.AllRowVarsExceptSingleCopy);
                    transWithEventInfo = CUDD.RestrictToFirst(transWithEventInfo, encoder.model.AllColVars);

                    lastValuation    = currentValuation;
                    currentValuation = encoder.GetValuationFromBDD(transWithEventInfo, this.InitialStep.GlobalEnv);

                    string eventName = encoder.GetEventChannelName(lastValuation, currentValuation, transWithEventInfo);

                    VerificationOutput.CounterExampleTrace.Add(new ConfigurationBDD(eventName, currentValuation));

                    //
                    CUDD.Deref(transWithEventInfo);
                }
            }

            //
            CUDD.Deref(automataBDD.transitionBDD, automataBDD.Ticks, traces);

            //
            VerificationOutput.ActualMemoryUsage = CUDD.ReadMemoryInUse();
            VerificationOutput.numberOfBoolVars  = encoder.model.NumberOfBoolVars;

            encoder.model.Close();
        }
Exemplo n.º 2
0
        /// <summary>
        /// Return a computation of a buchi automata in form "prefix (period)*"
        /// [ REFS: 'prefix, period', DEREFS:]
        /// </summary>
        /// <param name="automataBDD"></param>
        /// <param name="model"></param>
        public void MC(AutomataBDD automataBDD, Model model)
        {
            //Clear the old data
            this.transitionsNoEvents.Clear();
            this.prefix.Clear();
            this.period.Clear();

            ExpressionBDDEncoding initEncoding = automataBDD.initExpression.TranslateBoolExpToBDD(model);

            if (initEncoding.GuardDDs.Count == 0)
            {
                return;
            }

            ExpressionBDDEncoding finalStateEncoding = automataBDD.acceptanceExpression.TranslateBoolExpToBDD(model);

            if (finalStateEncoding.GuardDDs.Count == 0)
            {
                return;
            }

            CUDDNode initState             = CUDD.Function.Or(initEncoding.GuardDDs);
            CUDDNode finalState            = CUDD.Function.Or(finalStateEncoding.GuardDDs);
            CUDDNode finalStateWithNoEvent = CUDD.Abstract.ThereExists(finalState, model.GetAllEventVars());

            CUDD.Ref(automataBDD.transitionBDD);
            this.transitionsNoEvents = CUDD.Abstract.ThereExists(automataBDD.transitionBDD, model.GetAllEventVars());

            CUDDNode allSCCs = SCCHull(model, initState, finalStateWithNoEvent);

            if (!allSCCs.Equals(CUDD.ZERO) && VerificationOutput.GenerateCounterExample)
            {
                this.VerificationOutput.VerificationResult = VerificationResultType.INVALID;

                //Transitions out from allSCCs
                CUDD.Ref(transitionsNoEvents);
                CUDD.Ref(allSCCs);
                List <CUDDNode> R = CUDD.Function.And(transitionsNoEvents, allSCCs);

                //pick one state from the set final
                CUDD.Ref(allSCCs);
                CUDDNode s = CUDD.RestrictToFirst(allSCCs, model.AllRowVars);

                //while the states from which we can reach s are not all states that can be reached from s
                CUDDNode scc;
                while (true)
                {
                    CUDD.Ref(s);
                    CUDDNode backwardOfS = model.PredecessorsStart(s, R);

                    CUDD.Ref(s);
                    CUDDNode forwardOfS = model.SuccessorsStart(s, R);

                    //
                    CUDD.Ref(backwardOfS, forwardOfS);
                    CUDDNode temp = CUDD.Function.Different(backwardOfS, forwardOfS);
                    if (temp.Equals(CUDD.ZERO))
                    {
                        scc = backwardOfS;
                        CUDD.Deref(forwardOfS, temp);
                        break;
                    }
                    else
                    {
                        CUDD.Deref(backwardOfS, forwardOfS, s);
                        s = CUDD.RestrictToFirst(temp, model.AllRowVars);
                    }
                }

                //R now contains only transitions within the SCC scc
                CUDD.Ref(scc, scc, scc, scc);
                R[0] = CUDD.Function.And(CUDD.Function.And(R[0], scc), model.SwapRowColVars(scc));
                R[1] = CUDD.Function.And(CUDD.Function.And(R[1], scc), model.SwapRowColVars(scc));

                CUDD.Ref(scc);
                CUDDNode notInSCC = CUDD.Function.Not(scc);

                List <CUDDNode> transitionNotInSCC = new List <CUDDNode>();

                CUDD.Ref(transitionsNoEvents, transitionsNoEvents);
                CUDD.Ref(notInSCC, notInSCC);
                transitionNotInSCC.AddRange(CUDD.Function.And(transitionsNoEvents, notInSCC));
                transitionNotInSCC.AddRange(CUDD.Function.And(transitionsNoEvents, model.SwapRowColVars(notInSCC)));


                //prefix is now a shortest path from an initial state to a state in final
                model.Path(initState, scc, transitionNotInSCC, prefix, true);
                CUDD.Deref(transitionNotInSCC[0], transitionNotInSCC[1]);

                //Dummy value
                period.Add((prefix.Count == 0) ? initState : prefix[prefix.Count - 1]);

                //cycle must pass final state
                CUDD.Ref(period);
                CUDD.Ref(finalStateWithNoEvent);
                CUDDNode temp1 = CUDD.Function.And(CUDD.Function.Or(period), finalStateWithNoEvent);
                if (temp1.Equals(CUDD.ZERO))
                {
                    CUDD.Ref(scc, finalStateWithNoEvent);
                    CUDDNode acceptanceStateInCyle = CUDD.Function.And(scc, finalStateWithNoEvent);
                    model.Path(period[period.Count - 1], acceptanceStateInCyle, R, period, true);

                    CUDD.Deref(acceptanceStateInCyle);
                }
                CUDD.Deref(temp1);

                //
                bool isEmptyPathAllowed = period.Count != 1;
                model.Path(period[period.Count - 1], period[0], R, period, isEmptyPathAllowed);


                //Remove dummy
                CUDD.Deref(period[0]); period.RemoveAt(0);

                //
                CUDD.Deref(initState, finalStateWithNoEvent, allSCCs, s, scc, notInSCC);
                CUDD.Deref(transitionsNoEvents[0], transitionsNoEvents[1]);
                CUDD.Deref(R[0], R[1]);
            }
            else
            {
                this.VerificationOutput.VerificationResult = VerificationResultType.VALID;
                CUDD.Deref(initState, finalStateWithNoEvent, allSCCs);
                CUDD.Deref(transitionsNoEvents[0], transitionsNoEvents[1]);
            }
        }
Exemplo n.º 3
0
        /// <summary>
        /// Return the path from source to destination. If reachable, return true and path contains the path from source to destination
        /// [ REFS: '', DEREFS:]
        /// </summary>
        /// <param name="source"></param>
        /// <param name="destination"></param>
        /// <param name="transitions"></param>
        /// <param name="model"></param>
        /// <param name="path">not include the init state</param>
        /// <param name="isEmptyPathAllowed">if false, then the path must be not empty though the source satisfies the destination</param>
        /// <returns></returns>
        public bool PathBackWard(CUDDNode source, CUDDNode destination, List <CUDDNode> transitions, List <CUDDNode> path, bool isEmptyPathAllowed)
        {
            if (isEmptyPathAllowed)
            {
                //
                CUDD.Ref(source, destination);
                CUDDNode temp = CUDD.Function.And(source, destination);

                //In case source already satisfies destination
                if (!temp.Equals(CUDD.ZERO))
                {
                    CUDD.Deref(temp);
                    return(true);
                }
            }

            //
            bool            reachable = false;
            List <CUDDNode> backtrackingReachability = new List <CUDDNode>();

            CUDDNode allReachabeToGoal, currentReachableToGoal;

            CUDD.Ref(destination, destination);
            allReachabeToGoal = currentReachableToGoal = destination;

            int numberOfLoop = 0;

            CUDDNode commonNode = CUDD.Constant(0);

            do
            {
                //backward
                numberOfLoop++;
                Debug.Write(numberOfLoop + " ");

                //to find source state, now the currentReachableToGoal must be in column form (target state)
                currentReachableToGoal = this.SwapRowColVars(currentReachableToGoal);

                CUDD.Ref(transitions);
                currentReachableToGoal = CUDD.Function.And(currentReachableToGoal, transitions);

                CUDD.Ref(currentReachableToGoal);
                backtrackingReachability.Add(currentReachableToGoal);

                //get source state, but in row-form
                currentReachableToGoal = CUDD.Abstract.ThereExists(currentReachableToGoal, AllColVars);

                //Check 2 directions have intersection
                CUDD.Ref(currentReachableToGoal, source);
                CUDD.Deref(commonNode);
                commonNode = CUDD.Function.And(currentReachableToGoal, source);

                if (!commonNode.Equals(CUDD.ZERO))
                {
                    reachable = true;
                    break;
                }

                //find fixpoint
                CUDD.Ref(currentReachableToGoal, allReachabeToGoal);
                CUDDNode allReachabeToGoalTemp = CUDD.Function.Or(allReachabeToGoal, currentReachableToGoal);

                if (allReachabeToGoalTemp.Equals(allReachabeToGoal))
                {
                    reachable = false;
                    CUDD.Deref(allReachabeToGoalTemp);
                    break;
                }
                else
                {
                    currentReachableToGoal = CUDD.Function.Different(currentReachableToGoal, allReachabeToGoal);
                    allReachabeToGoal      = allReachabeToGoalTemp;
                }
            } while (true);

            Debug.WriteLine("\nPath Backward: " + numberOfLoop + " loops.");

            if (!reachable)
            {
                CUDD.Deref(currentReachableToGoal, allReachabeToGoal, commonNode);
                CUDD.Deref(backtrackingReachability);

                //
                backtrackingReachability.Clear();
            }
            else
            {
                //
                CUDD.Deref(currentReachableToGoal, allReachabeToGoal, commonNode);

                //backtrackingReachability contains transitions from source to destination
                if (backtrackingReachability.Count > 0)
                {
                    CUDDNode currentStateDD = source;
                    CUDD.Ref(currentStateDD);

                    for (int i = backtrackingReachability.Count - 1; i >= 0; i--)
                    {
                        //find the intersection
                        currentStateDD = CUDD.Function.And(backtrackingReachability[i], currentStateDD);

                        //remove all boolean variables in the row-form, it is now set of all reachable backward state
                        currentStateDD = CUDD.Abstract.ThereExists(currentStateDD, AllRowVars);

                        currentStateDD = CUDD.RestrictToFirst(currentStateDD, this.AllColVars);
                        currentStateDD = CUDD.Abstract.ThereExists(currentStateDD, this.GetAllEventVars());

                        //swap to row-variable form
                        currentStateDD = this.SwapRowColVars(currentStateDD);

                        CUDD.Ref(currentStateDD);
                        path.Add(currentStateDD);
                    }

                    //
                    CUDD.Deref(currentStateDD);
                }
            }
            //
            return(reachable);
        }
Exemplo n.º 4
0
        /// <summary>
        /// Return the path from source to destination. If reachable, return true and path contains the path from source to destination
        /// [ REFS: '', DEREFS:]
        /// </summary>
        /// <param name="source"></param>
        /// <param name="destination"></param>
        /// <param name="transitions"></param>
        /// <param name="model"></param>
        /// <param name="path">not include the init state</param>
        /// <param name="isEmptyPathAllowed">if false, then the path must be not empty though the source satisfies the destination</param>
        /// <returns></returns>
        public bool PathForward(CUDDNode source, CUDDNode destination, List <CUDDNode> transitions, List <CUDDNode> path, bool isEmptyPathAllowed)
        {
            if (isEmptyPathAllowed)
            {
                CUDD.Ref(source, destination);
                CUDDNode temp = CUDD.Function.And(source, destination);

                //In case source already satisfies destination
                if (!temp.Equals(CUDD.ZERO))
                {
                    CUDD.Deref(temp);
                    return(true);
                }
            }

            //
            bool            reachable           = false;
            List <CUDDNode> forwardReachability = new List <CUDDNode>();

            CUDDNode allReachableFromInit, currentReachableFromInit;

            CUDD.Ref(source, source);
            allReachableFromInit = currentReachableFromInit = source;

            int numberOfLoop = 0;

            CUDDNode commonNode = CUDD.Constant(0);

            do
            {
                //forward
                numberOfLoop++;
                Debug.Write(numberOfLoop + " ");

                CUDD.Ref(transitions);
                currentReachableFromInit = CUDD.Function.And(currentReachableFromInit, transitions);

                //Must reference because current value of currentReachableDD belonging to backtrackingReachability
                CUDD.Ref(currentReachableFromInit);
                forwardReachability.Add(currentReachableFromInit);

                currentReachableFromInit = this.SwapRowColVars(currentReachableFromInit);

                //get target state, in row-form
                currentReachableFromInit = CUDD.Abstract.ThereExists(currentReachableFromInit, AllColVars);

                //Check 2 directions have intersection
                CUDD.Ref(destination, currentReachableFromInit);
                CUDD.Deref(commonNode);
                commonNode = CUDD.Function.And(destination, currentReachableFromInit);

                if (!commonNode.Equals(CUDD.ZERO))
                {
                    reachable = true;
                    break;
                }

                //find fixpoint
                CUDD.Ref(currentReachableFromInit, allReachableFromInit);
                CUDDNode allReachabeFromInitTemp = CUDD.Function.Or(currentReachableFromInit, allReachableFromInit);

                if (allReachabeFromInitTemp.Equals(allReachableFromInit))
                {
                    //reachable = false;
                    CUDD.Deref(allReachabeFromInitTemp);
                    break;
                }
                else
                {
                    currentReachableFromInit = CUDD.Function.Different(currentReachableFromInit, allReachableFromInit);
                    allReachableFromInit     = allReachabeFromInitTemp;
                }
            } while (true);

            Debug.WriteLine("\nPath Forward: " + numberOfLoop + " loops.");

            if (!reachable)
            {
                CUDD.Deref(currentReachableFromInit, allReachableFromInit, commonNode);
                CUDD.Deref(forwardReachability);

                //
                forwardReachability.Clear();
            }
            else
            {
                //
                CUDD.Deref(currentReachableFromInit, allReachableFromInit);

                //in column form
                commonNode = this.SwapRowColVars(commonNode);
                for (int i = forwardReachability.Count - 1; i >= 0; i--)
                {
                    forwardReachability[i] = CUDD.Function.And(forwardReachability[i], commonNode);

                    //in column form
                    CUDD.Ref(forwardReachability[i]);
                    commonNode = CUDD.Abstract.ThereExists(forwardReachability[i], AllColVars);
                    commonNode = this.SwapRowColVars(commonNode);
                }

                CUDD.Deref(commonNode);

                //backtrackingReachability contains transitions from source to destination
                if (forwardReachability.Count > 0)
                {
                    CUDDNode currentStateDD = source;
                    CUDD.Ref(currentStateDD);

                    for (int i = 0; i < forwardReachability.Count; i++)
                    {
                        //find the intersection
                        currentStateDD = CUDD.Function.And(forwardReachability[i], currentStateDD);

                        //remove all boolean variables in the row-form, it is now set of all reachable backward state
                        currentStateDD = CUDD.Abstract.ThereExists(currentStateDD, AllRowVars);

                        currentStateDD = CUDD.RestrictToFirst(currentStateDD, this.AllColVars);
                        currentStateDD = CUDD.Abstract.ThereExists(currentStateDD, this.GetAllEventVars());

                        //swap to row-variable form
                        currentStateDD = this.SwapRowColVars(currentStateDD);

                        CUDD.Ref(currentStateDD);
                        path.Add(currentStateDD);
                    }

                    //
                    CUDD.Deref(currentStateDD);
                }
            }
            //
            return(reachable);
        }
Exemplo n.º 5
0
        /// <summary>
        /// Return the path from source to destination.
        /// If reachable, return true and path contains the path from source to destination
        /// Serach from two direction: forward and backward
        /// [ REFS: '', DEREFS:]
        /// </summary>
        /// <param name="source"></param>
        /// <param name="destination"></param>
        /// <param name="transitions">transitions[0]: normal transitions, transitions[1]: priority transition</param>
        /// <param name="model"></param>
        /// <param name="path">not include the init state</param>
        /// <param name="isEmptyPathAllowed">if false, then the path must be not empty though the source satisfies the destination</param>
        /// <returns></returns>
        public bool Path(CUDDNode source, CUDDNode destination, List <List <CUDDNode> > transitions, List <CUDDNode> path, bool isEmptyPathAllowed)
        {
            if (isEmptyPathAllowed)
            {
                CUDD.Ref(source, destination);
                CUDDNode temp = CUDD.Function.And(source, destination);

                //In case source already satisfies destination
                if (!temp.Equals(CUDD.ZERO))
                {
                    CUDD.Deref(temp);
                    return(true);
                }
            }

            //
            bool            reachable = false;
            List <CUDDNode> backtrackingReachability = new List <CUDDNode>();

            List <CUDDNode> forwardReachability = new List <CUDDNode>();

            CUDDNode allReachabeToGoal, currentReachableToGoal;

            CUDD.Ref(destination, destination);
            allReachabeToGoal = currentReachableToGoal = destination;

            CUDDNode allReachableFromInit, currentReachableFromInit;

            CUDD.Ref(source, source);
            allReachableFromInit = currentReachableFromInit = source;

            int nextStep              = 0;
            int forwardStep           = 0;
            int numberOfForwardNodes  = 0;
            int backwardSteps         = 0;
            int numberOfBackwardNodes = 0;

            CUDDNode commonNode = CUDD.Constant(0);

            do
            {
                if (nextStep != GoForward)
                {
                    //backward
                    Debug.WriteLine("Backward: " + numberOfBDDOperation);
                    numberOfBDDOperation++;

                    //to find source state, now the currentReachableToGoal must be in column form (target state)
                    currentReachableToGoal = this.SwapRowColVars(currentReachableToGoal);
                    currentReachableToGoal = TransitionsByTargetStates(currentReachableToGoal, transitions);

                    CUDD.Ref(currentReachableToGoal);
                    backtrackingReachability.Add(currentReachableToGoal);

                    //get source state, but in row-form
                    currentReachableToGoal = CUDD.Abstract.ThereExists(currentReachableToGoal, AllColVars);

                    //Check 2 directions have intersection
                    CUDD.Ref(currentReachableToGoal, currentReachableFromInit);
                    CUDD.Deref(commonNode);
                    commonNode = CUDD.Function.And(currentReachableToGoal, currentReachableFromInit);

                    if (!commonNode.Equals(CUDD.ZERO))
                    {
                        reachable = true;
                        break;
                    }

                    //find fixpoint
                    CUDD.Ref(currentReachableToGoal, allReachabeToGoal);
                    CUDDNode allReachabeToGoalTemp = CUDD.Function.Or(allReachabeToGoal, currentReachableToGoal);

                    backwardSteps++;
                    numberOfBackwardNodes = CUDD.GetNumNodes(allReachabeToGoalTemp);

                    if (allReachabeToGoalTemp.Equals(allReachabeToGoal))
                    {
                        reachable = false;
                        CUDD.Deref(allReachabeToGoalTemp);
                        break;
                    }
                    else
                    {
                        currentReachableToGoal = CUDD.Function.Different(currentReachableToGoal, allReachabeToGoal);
                        allReachabeToGoal      = allReachabeToGoalTemp;
                    }
                }

                if (nextStep != GoBackward)
                {
                    //forward
                    Debug.WriteLine("Forward: " + numberOfBDDOperation);
                    numberOfBDDOperation++;

                    currentReachableFromInit = TransitionsBySourceStates(currentReachableFromInit, transitions);

                    //Must reference because current value of currentReachableDD belonging to backtrackingReachability
                    CUDD.Ref(currentReachableFromInit);
                    forwardReachability.Add(currentReachableFromInit);

                    currentReachableFromInit = this.SwapRowColVars(currentReachableFromInit);

                    //get target state, in row-form
                    currentReachableFromInit = CUDD.Abstract.ThereExists(currentReachableFromInit, AllColVars);

                    //Check 2 directions have intersection
                    CUDD.Ref(currentReachableToGoal, currentReachableFromInit);
                    CUDD.Deref(commonNode);
                    commonNode = CUDD.Function.And(currentReachableToGoal, currentReachableFromInit);

                    if (!commonNode.Equals(CUDD.ZERO))
                    {
                        reachable = true;
                        break;
                    }

                    //find fixpoint
                    CUDD.Ref(currentReachableFromInit, allReachableFromInit);
                    CUDDNode allReachabeFromInitTemp = CUDD.Function.Or(currentReachableFromInit, allReachableFromInit);

                    forwardStep++;
                    numberOfForwardNodes = CUDD.GetNumNodes(allReachabeFromInitTemp);

                    if (allReachabeFromInitTemp.Equals(allReachableFromInit))
                    {
                        reachable = false;
                        CUDD.Deref(allReachabeFromInitTemp);
                        break;
                    }
                    else
                    {
                        currentReachableFromInit = CUDD.Function.Different(currentReachableFromInit, allReachableFromInit);
                        allReachableFromInit     = allReachabeFromInitTemp;
                    }
                }

                nextStep = GetNextStep(forwardStep, numberOfForwardNodes, backwardSteps, numberOfBackwardNodes);
            } while (true);

            if (!reachable)
            {
                CUDD.Deref(currentReachableToGoal, allReachabeToGoal, currentReachableFromInit, allReachableFromInit, commonNode);
                CUDD.Deref(backtrackingReachability);
                CUDD.Deref(forwardReachability);

                //
                backtrackingReachability.Clear();
                forwardReachability.Clear();
            }
            else
            {
                //
                CUDD.Deref(currentReachableToGoal, allReachabeToGoal, currentReachableFromInit, allReachableFromInit);

                //in column form
                commonNode = this.SwapRowColVars(commonNode);
                for (int i = forwardReachability.Count - 1; i >= 0; i--)
                {
                    //Kill commonNode
                    CUDDNode correctTransition = CUDD.Function.And(forwardReachability[i], commonNode);

                    CUDD.Ref(correctTransition);
                    backtrackingReachability.Add(correctTransition);


                    //in column form
                    commonNode = CUDD.Abstract.ThereExists(correctTransition, AllColVars);
                    commonNode = this.SwapRowColVars(commonNode);
                }

                CUDD.Deref(commonNode);

                //backtrackingReachability contains transitions from source to destination
                if (backtrackingReachability.Count > 0)
                {
                    CUDDNode currentStateDD = source;
                    CUDD.Ref(currentStateDD);

                    for (int i = backtrackingReachability.Count - 1; i >= 0; i--)
                    {
                        //find the intersection
                        currentStateDD = CUDD.Function.And(backtrackingReachability[i], currentStateDD);

                        //remove all boolean variables in the row-form, it is now set of all reachable backward state
                        currentStateDD = CUDD.Abstract.ThereExists(currentStateDD, AllRowVars);

                        currentStateDD = CUDD.RestrictToFirst(currentStateDD, this.AllColVars);
                        currentStateDD = CUDD.Abstract.ThereExists(currentStateDD, this.GetAllEventVars());

                        //swap to row-variable form
                        currentStateDD = this.SwapRowColVars(currentStateDD);

                        CUDD.Ref(currentStateDD);
                        path.Add(currentStateDD);
                    }

                    //
                    CUDD.Deref(currentStateDD);
                }
            }
            //
            return(reachable);
        }