diff --git a/Examples/OldDrawGrid/main.cpp b/Examples/OldDrawGrid/main.cpp index 9517ff4b7978b691c2744157541b57cbdae32262..933e61d1cb8fb404e9f023fe70210cda3cc4550a 100644 --- a/Examples/OldDrawGrid/main.cpp +++ b/Examples/OldDrawGrid/main.cpp @@ -4785,6 +4785,8 @@ void svg_draw(std::ostream & file) //svg_draw_faces(file,temp_boundary,modelview,projection,viewport); std::vector sorted_clip_boundary(clip_boundary); + for(INMOST_DATA_ENUM_TYPE q = 0; q < sorted_clip_boundary.size() ; q++) + sorted_clip_boundary[q].compute_dist(campos); face2gl::radix_sort_dist(sorted_clip_boundary); svg_draw_faces(file,sorted_clip_boundary,modelview,projection,viewport); diff --git a/Source/Autodiff/autodiff.cpp b/Source/Autodiff/autodiff.cpp index a296e4830463891218188a93421102edd0e2f2b2..5d2e38aecd57668974eefe020b03202f556e47cb 100644 --- a/Source/Autodiff/autodiff.cpp +++ b/Source/Autodiff/autodiff.cpp @@ -157,6 +157,7 @@ namespace INMOST } } } + std::set Pre, Post; //Nonlocal indices #if defined(USE_MPI) if (m->GetProcessorsNumber() > 1) { @@ -228,18 +229,99 @@ namespace INMOST } } } + //synchronize indices last_num += first_num; { std::vector exch_tags; for (index_enum::iterator it = index_tags.begin(); it != index_tags.end(); ++it) exch_tags.push_back(it->indices); m->ExchangeData(exch_tags, exch_mask,0); } + //compute out-of-bounds indices + for (index_enum::iterator it = index_tags.begin(); it != index_tags.end(); ++it) + { + for (ElementType etype = NODE; etype <= MESH; etype = etype << 1) + { + if (it->indices.isDefined(etype)) + { + exch_mask |= etype; + if (it->indices.GetSize() == ENUMUNDEF) + { + if (!it->indices.isSparse(etype)) + { + for (Mesh::iteratorStorage jt = m->Begin(etype); jt != m->End(); ++jt) + { + if ((!(etype & paralleltypes) || ((etype & paralleltypes) && jt->getAsElement()->GetStatus() == Element::Ghost)) && (it->d.domain_mask == 0 || jt->GetMarker(it->d.domain_mask))) + { + Storage::integer_array indarr = jt->IntegerArray(it->indices); + for (Storage::integer_array::iterator qt = indarr.begin(); qt != indarr.end(); ++qt) + { + if( *qt < first_num ) Pre.insert(*qt); + else if( *qt >= last_num ) Post.insert(*qt); + } + } + } + } + else + { + for (Mesh::iteratorStorage jt = m->Begin(etype); jt != m->End(); ++jt) + { + if ((!(etype & paralleltypes) || ((etype & paralleltypes) && jt->getAsElement()->GetStatus() == Element::Ghost)) && (jt->HaveData(it->d.t) && (it->d.domain_mask == 0 || jt->GetMarker(it->d.domain_mask)))) + { + Storage::integer_array indarr = jt->IntegerArray(it->indices); + indarr.resize(jt->RealArray(it->d.t).size()); + for (Storage::integer_array::iterator qt = indarr.begin(); qt != indarr.end(); ++qt) + { + if( *qt < first_num ) Pre.insert(*qt); + else if( *qt >= last_num ) Post.insert(*qt); + } + } + } + } + } + else //getsize + { + if (!it->indices.isSparse(etype)) + { + for (Mesh::iteratorStorage jt = m->Begin(etype); jt != m->End(); ++jt) + { + if ((!(etype & paralleltypes) || ((etype & paralleltypes) && jt->getAsElement()->GetStatus() == Element::Ghost)) && (it->d.domain_mask == 0 || jt->GetMarker(it->d.domain_mask))) + { + Storage::integer_array indarr = jt->IntegerArray(it->indices); + for (Storage::integer_array::iterator qt = indarr.begin(); qt != indarr.end(); ++qt) + { + if( *qt < first_num ) Pre.insert(*qt); + else if( *qt >= last_num ) Post.insert(*qt); + } + } + } + } + else + { + for (Mesh::iteratorStorage jt = m->Begin(etype); jt != m->End(); ++jt) + { + if ((!(etype & paralleltypes) || ((etype & paralleltypes) && jt->getAsElement()->GetStatus() == Element::Ghost)) && (jt->HaveData(it->d.t) && (it->d.domain_mask == 0 || jt->GetMarker(it->d.domain_mask)))) + { + Storage::integer_array indarr = jt->IntegerArray(it->indices); + for (Storage::integer_array::iterator qt = indarr.begin(); qt != indarr.end(); ++qt) + { + if( *qt < first_num ) Pre.insert(*qt); + else if( *qt >= last_num ) Post.insert(*qt); + } + } + } + } + } //getsize + } //isdefined + } //etype + } //it + // after cycle } #endif // this version will fail in parallel //merger.Resize(first_num,last_num,false); // use this version until there is a way to define multiple intervals in RowMerger - INMOST_DATA_INTEGER_TYPE max_unknowns = m->AggregateMax(static_cast(last_num)); + //INMOST_DATA_INTEGER_TYPE max_unknowns = m->AggregateMax(static_cast(last_num)); + //std::cout << "Proc " << m->GetProcessorRank() << " size " << last_num-first_num << " pre " << Pre.size() << " post " << Post.size() << " max " << max_unknowns << std::endl; #if defined(USE_OMP) #pragma omp parallel { @@ -247,10 +329,10 @@ namespace INMOST { merger.resize(omp_get_num_procs()); } - merger[omp_get_thread_num()].Resize(0,max_unknowns,false); + merger[omp_get_thread_num()].Resize(first_num,last_num,std::vector(Pre.begin(),Pre.end()),std::vector(Post.begin(),Post.end()),false); } #else - merger.Resize(0,max_unknowns,false); + merger.Resize(first_num,last_num,std::vector(Pre.begin(),Pre.end()),std::vector(Post.begin(),Post.end()),false); #endif } #endif //USE_MESH diff --git a/Source/Headers/inmost_sparse.h b/Source/Headers/inmost_sparse.h index f4c8ccd731d89b7ee56cd12c126e561caef2f15d..93b5d8f3359cbcb841cb02cf321ebb14a91c0ca2 100644 --- a/Source/Headers/inmost_sparse.h +++ b/Source/Headers/inmost_sparse.h @@ -503,8 +503,7 @@ namespace INMOST #endif //defined(USE_SOLVER) #if defined(USE_SOLVER) || defined(USE_AUTODIFF) - - /// This class may be used to sum multiple sparse rows. + /// This class may be used to sum multiple sparse rows. /// \warning /// In parallel column indices of the matrix may span wider then /// local row indices, to prevent any problem you are currently @@ -550,8 +549,26 @@ namespace INMOST private: bool Sorted; ///< Contents of linked list should be sorted. INMOST_DATA_ENUM_TYPE Nonzeros; ///< Number of nonzero in linked list. + INMOST_DATA_ENUM_TYPE IntervalBeg; ///< Begin of global interval of owned index interval + INMOST_DATA_ENUM_TYPE IntervalEnd; ///< End of global interval of owned index interval interval< INMOST_DATA_ENUM_TYPE, Row::entry > LinkedList; ///< Storage for linked list. + std::vector< INMOST_DATA_ENUM_TYPE > NonlocalPre; ///< List of global indices, that are to the left of owned index interval + std::vector< INMOST_DATA_ENUM_TYPE > NonlocalPost; ///< List of global indices, that are to the right of owned index interval public: + /// This function converts global index into local index. + /// @param pos Global index. + /// @return Local index. + INMOST_DATA_ENUM_TYPE MapIndex(INMOST_DATA_ENUM_TYPE pos) const; + /// This function converts local index into global index. + /// @param pos Local index. + /// @return Global index. + INMOST_DATA_ENUM_TYPE UnmapIndex(INMOST_DATA_ENUM_TYPE pos) const; + /// This function provides information about additional non-local indices. + /// \warning + /// All contents of linked list will be lost. + /// @param Pre Non-local indices that go before IntervalBegin. + /// @param Post Non-local indices that follow IntervalEnd. + void SetNonlocal(const std::vector & Pre, const std::vector & Post); /// Default constructor without size specified. RowMerger(); /// Constructor with size specified. @@ -559,30 +576,42 @@ namespace INMOST /// @param interval_end Last index in linked list. /// @param Sorted Result should be sorted or not. RowMerger(INMOST_DATA_ENUM_TYPE interval_begin, INMOST_DATA_ENUM_TYPE interval_end, bool Sorted = true); -#if defined(USE_SOLVER) - /// Constructor that gets sizes from the matrix. - /// @param A Matrix to get sizes from. - /// @param Sorted Result should be sorted. - RowMerger(Matrix & A, bool Sorted = true); -#endif //USE_SOLVER - /// Destructor. + /// Constructor with size and non-local mapping specified. + /// @param interval_begin First index in linked list. + /// @param interval_end Last index in linked list. + /// @param Pre Nonlocal indices before First index in linked list. + /// @param Post Nonlocal indices after Last index in linked list. + /// @param Sorted Result should be sorted or not. + RowMerger(INMOST_DATA_ENUM_TYPE interval_begin, INMOST_DATA_ENUM_TYPE interval_end, const std::vector & Pre, const std::vector & Post, bool Sorted = true); + /// Destructor. ~RowMerger(); /// Resize linked list for new interval. /// \warning /// All contents of linked list will be lost after resize. - /// This behavior may be changed in future. /// @param interval_begin First index in linked list. /// @param interval_end Last index in linked list. /// @param Sorted Result should be sorted or not. void Resize(INMOST_DATA_ENUM_TYPE interval_begin, INMOST_DATA_ENUM_TYPE interval_end, bool Sorted = true); + /// Resize linked list for new interval with non-local mapping. + /// \warning + /// All contents of linked list will be lost after resize. + /// @param interval_begin First index in linked list. + /// @param interval_end Last index in linked list. + /// @param Pre Nonlocal indices before First index in linked list. + /// @param Post Nonlocal indices after Last index in linked list. + /// @param Sorted Result should be sorted or not. + void Resize(INMOST_DATA_ENUM_TYPE interval_begin, INMOST_DATA_ENUM_TYPE interval_end, const std::vector & Pre, const std::vector & Post, bool Sorted = true); #if defined(USE_SOLVER) - /// Resize linked list for new matrix. + /// Constructor that gets sizes from the matrix, including non-local mapping. + /// @param A Matrix to get sizes from. + /// @param Sorted Result should be sorted. + RowMerger(const Matrix & A, bool Sorted = true); + /// Resize linked list for new matrix, including non-local mapping. /// \warning /// All contents of linked list will be lost after resize. - /// This behavior may be changed in future. /// @param A Matrix to get sizes from. /// @param Sorted Result should be sorted or not. - void Resize(Matrix & A, bool Sorted = true); + void Resize(const Matrix & A, bool Sorted = true); #endif //USE_SOLVER /// Clear linked list. void Clear(); @@ -593,14 +622,26 @@ namespace INMOST /// @param coef Coefficient to multiply row values. /// @param r A row to be added. /// @param PreSortRow Sort values of the row before adding. Will be activated only for sorted linked lists. - void PushRow(INMOST_DATA_REAL_TYPE coef, Row & r, bool PreSortRow = false); + void PushRow(INMOST_DATA_REAL_TYPE coef, Row & r, bool PreSortRow); /// Add a row with a coefficient into non-empty linked list. /// Use RowMerger::PushRow for empty linked list. /// @param coef Coefficient to multiply row values. /// @param r A row to be added. /// @param PreSortRow Sort values of the row before adding. Will be activated only for sorted linked lists. - void AddRow(INMOST_DATA_REAL_TYPE coef, Row & r, bool PreSortRow = false); - /// Multiply all entries of linked list by a coefficient. + void AddRow(INMOST_DATA_REAL_TYPE coef, Row & r, bool PreSortRow); + /// Add a row with a coefficient into empty linked list. + /// This routine should be a bit faster then RowMerger::AddRow + /// for empty linked list. It may result in an unexpected behavior + /// for non-empty linked list, asserts will fire in debug mode. + /// @param coef Coefficient to multiply row values. + /// @param r A row to be added. + void PushRow(INMOST_DATA_REAL_TYPE coef, const Row & r); + /// Add a row with a coefficient into non-empty linked list. + /// Use RowMerger::PushRow for empty linked list. + /// @param coef Coefficient to multiply row values. + /// @param r A row to be added. + void AddRow(INMOST_DATA_REAL_TYPE coef, const Row & r); + /// Multiply all entries of linked list by a coefficient. /// @param coef A coefficient for multiplication. void Multiply(INMOST_DATA_REAL_TYPE coef); /// Place entries from linked list into row. @@ -629,14 +670,16 @@ namespace INMOST /// @param a Row a. /// @param beta Multiplier for row b. /// @param b Row b. - void Merge(Row & c, INMOST_DATA_REAL_TYPE alpha, Row & a, INMOST_DATA_REAL_TYPE beta, Row & b) + void Merge(Row & c, INMOST_DATA_REAL_TYPE alpha, const Row & a, INMOST_DATA_REAL_TYPE beta, const Row & b) { PushRow(alpha,a); AddRow(beta,b); RetriveRow(c); Clear(); } + ///Retrive iterator for the first element. iterator Begin() {return iterator(&LinkedList);} + ///Retrive iterator for the position beyond the last element. iterator End() {iterator ret(&LinkedList); ret.pos = EOL; return ret;} }; #endif //defined(USE_SOLVER) || defined(USE_AUTODIFF) diff --git a/Source/IO/mesh_ecl_file.cpp b/Source/IO/mesh_ecl_file.cpp index 86015a41c89831b0eed6a3a288dc3723477a65da..9ee9b65c586d85b54b1facd4a52ceee809f5eb3a 100644 --- a/Source/IO/mesh_ecl_file.cpp +++ b/Source/IO/mesh_ecl_file.cpp @@ -802,7 +802,7 @@ namespace INMOST entry.j = jj; entry.k1 = kk1; entry.k2 = kk2; - if( openshut == "OPEN" ) + if( std::string(openshut) == "OPEN" ) entry.open = true; else entry.open = false; diff --git a/Source/Solver/sparse.cpp b/Source/Solver/sparse.cpp index cb5fe12474f0b4c8d35451244d0ba1837d0ab375..9c807f81083692c71a276ca8868b217f77f7cbe4 100644 --- a/Source/Solver/sparse.cpp +++ b/Source/Solver/sparse.cpp @@ -58,10 +58,11 @@ namespace INMOST ////////class RowMerger - RowMerger::RowMerger() : Sorted(true), Nonzeros(0) {} + RowMerger::RowMerger() : Sorted(true), Nonzeros(0), IntervalBeg(0), IntervalEnd(0) {} INMOST_DATA_REAL_TYPE & RowMerger::operator[] (INMOST_DATA_ENUM_TYPE pos) { + pos = MapIndex(pos); if( LinkedList[pos+1].first != UNDEF ) return LinkedList[pos+1].second; else { @@ -94,46 +95,117 @@ namespace INMOST INMOST_DATA_REAL_TYPE RowMerger::operator[] (INMOST_DATA_ENUM_TYPE pos) const { + pos = MapIndex(pos); if( LinkedList[pos+1].first != UNDEF ) return LinkedList[pos+1].second; else throw -1; } RowMerger::RowMerger(INMOST_DATA_ENUM_TYPE interval_begin, INMOST_DATA_ENUM_TYPE interval_end, bool Sorted) - : Sorted(Sorted), Nonzeros(0), LinkedList(interval_begin,interval_end+1,Row::make_entry(UNDEF,0.0)) + : Sorted(Sorted), Nonzeros(0), IntervalBeg(interval_begin), IntervalEnd(interval_end), LinkedList(interval_begin,interval_end+1,Row::make_entry(UNDEF,0.0)) { LinkedList.begin()->first = EOL; } + RowMerger::RowMerger(INMOST_DATA_ENUM_TYPE interval_begin, INMOST_DATA_ENUM_TYPE interval_end, const std::vector & Pre, const std::vector & Post, bool Sorted) + : Sorted(Sorted), Nonzeros(0), IntervalBeg(interval_begin), IntervalEnd(interval_end), NonlocalPre(Pre), NonlocalPost(Post), LinkedList(interval_begin-Pre.size(),interval_end+Post.size()+1,Row::make_entry(UNDEF,0.0)) + { + assert(std::is_sorted(Pre.begin(),Pre.end())); + assert(std::is_sorted(Post.begin(),Post.end())); + LinkedList.begin()->first = EOL; + } + void RowMerger::Resize(INMOST_DATA_ENUM_TYPE interval_begin, INMOST_DATA_ENUM_TYPE interval_end, bool _Sorted) { - LinkedList.set_interval_beg(interval_begin); - LinkedList.set_interval_end(interval_end+1); + LinkedList.set_interval_beg(interval_begin-NonlocalPre.size()); + LinkedList.set_interval_end(interval_end+1+NonlocalPost.size()); + IntervalBeg = interval_begin; + IntervalEnd = interval_end; std::fill(LinkedList.begin(),LinkedList.end(),Row::make_entry(UNDEF,0.0)); LinkedList.begin()->first = EOL; Nonzeros = 0; Sorted = _Sorted; } + + void RowMerger::Resize(INMOST_DATA_ENUM_TYPE interval_begin, INMOST_DATA_ENUM_TYPE interval_end, const std::vector & Pre, const std::vector & Post, bool _Sorted) + { + NonlocalPre = Pre; + NonlocalPost = Post; + Resize(interval_begin,interval_end,_Sorted); + } #if defined(USE_SOLVER) - void RowMerger::Resize(Matrix & A, bool _Sorted) + void RowMerger::Resize(const Matrix & A, bool _Sorted) { - INMOST_DATA_ENUM_TYPE mbeg, mend; + INMOST_DATA_ENUM_TYPE mbeg, mend, k, l, ind; + //retrive interval of indices A.GetInterval(mbeg,mend); + //gather non-local mapping from matrix + std::set Pre, Post; + for(k = mbeg; k < mend; ++k) + { + for(l = 0; l < A[k].Size(); ++l) + { + ind = A[k].GetIndex(l); + if( ind < mbeg ) Pre.insert(ind); + else if( ind >= mend ) Post.insert(ind); + } + } + //sort to prepare for binary search + NonlocalPre.clear(); + NonlocalPre.insert(NonlocalPre.end(),Pre.begin(),Pre.end()); + NonlocalPost.clear(); + NonlocalPre.insert(NonlocalPost.end(),Post.begin(),Post.end()); Resize(mbeg,mend,_Sorted); } - RowMerger::RowMerger(Matrix & A, bool Sorted) : Sorted(Sorted), Nonzeros(0) + RowMerger::RowMerger(const Matrix & A, bool Sorted) : Sorted(Sorted), Nonzeros(0) { - INMOST_DATA_ENUM_TYPE mbeg, mend; - A.GetInterval(mbeg,mend); - LinkedList.set_interval_beg(mbeg); - LinkedList.set_interval_end(mend+1); - std::fill(LinkedList.begin(),LinkedList.end(),Row::make_entry(UNDEF,0.0)); - LinkedList.begin()->first = EOL; + Resize(A,Sorted); } #endif RowMerger::~RowMerger() {} + void RowMerger::SetNonlocal(const std::vector & Pre, const std::vector & Post) + { + assert(std::is_sorted(Pre.begin(),Pre.end())); + assert(std::is_sorted(Post.begin(),Post.end())); + NonlocalPre = Pre; + NonlocalPost = Post; + Resize(IntervalBeg,IntervalEnd,Sorted); + } + + + INMOST_DATA_ENUM_TYPE RowMerger::MapIndex(INMOST_DATA_ENUM_TYPE pos) const + { + if( pos < IntervalBeg ) + { + if(NonlocalPre.empty()) + std::cout << "pre " << NonlocalPre.size() << " post " << NonlocalPost.size() << " pos " << pos << " beg " << IntervalBeg << " end " << IntervalEnd << std::endl; + assert(!NonlocalPre.empty()); //there are indices provided + std::vector< INMOST_DATA_ENUM_TYPE >::const_iterator search = std::lower_bound(NonlocalPre.begin(),NonlocalPre.end(),pos); + assert(*search == pos); //is there such index? + return IntervalBeg - NonlocalPre.size() + static_cast(search-NonlocalPre.begin()); + } + else if( pos >= IntervalEnd ) + { + assert(!NonlocalPost.empty()); //there are indices provided + std::vector< INMOST_DATA_ENUM_TYPE >::const_iterator search = std::lower_bound(NonlocalPost.begin(),NonlocalPost.end(),pos); + assert(*search == pos); //is there such index? + return IntervalEnd + static_cast(search-NonlocalPost.begin()); + } + return pos; + } + + INMOST_DATA_ENUM_TYPE RowMerger::UnmapIndex(INMOST_DATA_ENUM_TYPE pos) const + { + if( pos < IntervalBeg ) + return NonlocalPre[pos+NonlocalPre.size()-IntervalBeg]; + else if( pos >= IntervalEnd ) + return NonlocalPost[pos-IntervalEnd]; + return pos; + } + + void RowMerger::Clear() { INMOST_DATA_ENUM_TYPE i = LinkedList.begin()->first, j; @@ -148,20 +220,27 @@ namespace INMOST Nonzeros = 0; } + void RowMerger::PushRow(INMOST_DATA_REAL_TYPE coef, Row & r, bool PreSortRow) { if( Sorted && PreSortRow ) std::sort(r.Begin(),r.End()); - //if( Nonzeros != 0 ) printf("nnz %d link %p proc %d\n",Nonzeros,this,omp_get_thread_num()); + PushRow(coef,r); + } + + void RowMerger::PushRow(INMOST_DATA_REAL_TYPE coef, const Row & r) + { assert(Nonzeros == 0); //Linked list should be empty assert(LinkedList.begin()->first == EOL); //again check that list is empty INMOST_DATA_ENUM_TYPE index = LinkedList.get_interval_beg(); - Row::iterator it = r.Begin(), jt; + Row::const_iterator it = r.Begin(), jt; while( it != r.End() ) { - LinkedList[index].first = it->first+1; - LinkedList[it->first+1].first = EOL; - LinkedList[it->first+1].second = it->second*coef; - index = it->first+1; + INMOST_DATA_ENUM_TYPE pos = it->first; + pos = MapIndex(pos); + LinkedList[index].first = pos+1; + LinkedList[pos+1].first = EOL; + LinkedList[pos+1].second = it->second*coef; + index = pos+1; ++Nonzeros; jt = it; ++it; @@ -172,32 +251,39 @@ namespace INMOST void RowMerger::AddRow(INMOST_DATA_REAL_TYPE coef, Row & r, bool PreSortRow) { if( Sorted && PreSortRow ) std::sort(r.Begin(),r.End()); + AddRow(coef,r); + } + + void RowMerger::AddRow(INMOST_DATA_REAL_TYPE coef, const Row & r) + { INMOST_DATA_ENUM_TYPE index = LinkedList.get_interval_beg(), next; - Row::iterator it = r.Begin(), jt; + Row::const_iterator it = r.Begin(), jt; while( it != r.End() ) { - if( LinkedList[it->first+1].first != UNDEF ) - LinkedList[it->first+1].second += coef*it->second; + INMOST_DATA_ENUM_TYPE pos = it->first; + pos = MapIndex(pos); + if( LinkedList[pos+1].first != UNDEF ) + LinkedList[pos+1].second += coef*it->second; else if( Sorted ) { next = index; - while(next < it->first+1) + while(next < pos+1) { index = next; next = LinkedList[index].first; } - assert(index < it->first+1); - assert(it->first+1 < next); - LinkedList[index].first = it->first+1; - LinkedList[it->first+1].first = next; - LinkedList[it->first+1].second = coef*it->second; + assert(index < pos+1); + assert(pos+1 < next); + LinkedList[index].first = pos+1; + LinkedList[pos+1].first = next; + LinkedList[pos+1].second = coef*it->second; ++Nonzeros; } else { - LinkedList[it->first+1].first = LinkedList[index].first; - LinkedList[it->first+1].second = coef*it->second; - LinkedList[index].first = it->first+1; + LinkedList[pos+1].first = LinkedList[index].first; + LinkedList[pos+1].second = coef*it->second; + LinkedList[index].first = pos+1; ++Nonzeros; } jt = it; @@ -206,6 +292,7 @@ namespace INMOST } } + void RowMerger::RetriveRow(Row & r) { r.Resize(Nonzeros); @@ -214,7 +301,7 @@ namespace INMOST { if( LinkedList[i].second ) { - r.GetIndex(k) = i-1; + r.GetIndex(k) = UnmapIndex(i-1); r.GetValue(k) = LinkedList[i].second; ++k; }