// -*- C++ -*- #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef DIVINE_TIMED_URA_H #define DIVINE_TIMED_URA_H namespace divine { namespace timed { namespace Ura { static const int inf = std::numeric_limits< int >::max(); struct guard { // simple guard of the form "< x" or "<= x" int val; bool open; // true: <, false: <= guard( int v, bool o ) : val( v ), open( o ) {} guard() : val( 0 ), open( false ) {} bool operator <( const guard &other ) const { if ( val < other.val ) return true; if ( val > other.val ) return false; return open > other.open; // open && !other.open } guard operator +( guard rhs ) const { if ( rhs.val == inf || val == inf ) rhs.val = inf; else { rhs.val += val; rhs.open = rhs.open || open; } return rhs; } bool operator ==( const guard &other ) const { return val == other.val && open == other.open; } friend std::ostream &operator <<( std::ostream &out, const guard &g ) { out << ( g.open ? " <" : "<=" ); if ( g.val == inf ) out << "inf"; else out << g.val; return out; } static guard less_than( int val ) { return { val, true }; } static guard less_eq( int val ) { return { val, false }; } }; struct interval_partition { // partition of the (non-negative) real line into intervals // (n+1) intervals represented by n guards std::vector< guard > guards; bool from_zero = true; interval_partition() {} void output_interval( int index, std::ostream &out ) { if ( index == 0 ) { out << ( from_zero ? "[0," : "(-inf," ); } else { auto & prev = guards[ index - 1 ]; out << ( prev.open ? '[' : '(' ) << prev.val << ','; } if ( index == last() ) { out << "+inf)"; } else { auto & next = guards[ index ]; out << next.val << ( next.open ? ')' : ']' ); } } void add_guard( const guard g ) { if ( std::find( guards.begin(), guards.end(), g ) == guards.end() ) guards.push_back( g ); } std::pair< guard, guard > to_dbm_guards( int index ) { std::pair< guard, guard > ret; // change (x - y) \in interval into // (y - x) 0 && guard::less_than( value ) < guards[ index - 1 ] ) return -1; if ( index < int( guards.size() ) && !( guard::less_than( value ) < guards[ index ] ) ) return 1; return 0; } friend std::ostream &operator <<( std::ostream &out, const interval_partition &p ) { out << ( p.from_zero ? "[0," : "(-inf," ); for ( auto &g : p.guards ) { out << g.val << ( g.open ? ")[" : "](" ) << g.val << ','; } out << "+inf)"; return out; } static interval_partition make_diagonal_partition( const interval_partition &px, const interval_partition &py ) { interval_partition ret; std::set< int > aux; for ( auto &x : px.guards ) { for ( auto &y : py.guards ) { aux.insert( x.val - y.val ); } } for ( auto val : aux ) { ret.guards.push_back( guard::less_than( val ) ); ret.guards.push_back( guard::less_eq( val ) ); } ret.from_zero = false; return ret; } }; struct dbm { int size; guard *matrix; dbm( int s ) : size( s ), matrix( new guard[ size * size ]() ) {} dbm( const dbm &other ) : size( other.size ), matrix( new guard[ size * size ]() ) { std::copy( other.matrix, other.matrix + size*size, matrix ); } dbm( dbm && other ) : size( other.size ), matrix ( other.matrix ) { other.matrix = nullptr; } guard &operator()( int x, int y ) { return matrix[ x + y * size ]; } const guard &operator()( int x, int y ) const { return matrix[ x + y * size ]; } ~dbm() { delete [] matrix; } void dump( std::ostream &out ) const { for ( int i = 0; i < size; ++i ) { for ( int j = 0; j < size; ++j ) { std::stringstream s; s << (*this)( i, j ); out << std::setw( 7 ) << std::left << s.str(); } out << std::endl; } } void minimize() { // Floyd-Warshall, yay for ( int k = 0; k < size; ++k ) for ( int i = 0; i < size; ++i ) for ( int j = 0; j < size; ++j ) { guard aux = (*this)( i, k ) + (*this)( k, j ); if ( aux < (*this)( i, j ) ) (*this)( i, j ) = aux; } } bool neg_diagonal() const { for ( int i = 0; i < size; ++i ) { if ( (*this)( i, i ) < guard::less_eq( 0 ) ) return true; } return false; } bool empty() { minimize(); return neg_diagonal(); } void intersect( const dbm &other ) { for ( int i = 0; i < size; ++i ) for ( int j = 0; j < size; ++j ) if ( other( i, j ) < (*this)( i, j ) ) (*this)( i, j ) = other( i, j ); } }; struct ura { //std::vector< std::string > clock_names; std::vector< interval_partition > clock_guards; std::vector< std::vector< interval_partition > > diagonal_guards; // diagonal_guards[ y ][ x ] contains the partition for x - y int num_clocks = 0; void add_guard( unsigned clock, int value, bool open ) { ASSERT_LEQ( clock, clock_guards.size() - 1 ); clock_guards[ clock ].add_guard( guard( value, open ) ); } void set_dimension( int dim ) { clock_guards.resize( dim ); num_clocks = dim; } void finalize() { if ( !dbm_rep.empty() ) { dbm_rep.clear(); transitions.clear(); uppaal_dbm_rep.clear(); diagonal_guards.clear(); visited.clear(); } for ( int clk = 0; clk < num_clocks; ++clk ) std::sort( clock_guards[ clk ].guards.begin(), clock_guards[ clk ].guards.end() ); for ( int i = 0; i < num_clocks; ++i ) { diagonal_guards.emplace_back(); auto &diag = diagonal_guards.back(); for ( int j = 0; j < i; ++j ) { diag.push_back( interval_partition::make_diagonal_partition( clock_guards[ j ], clock_guards[ i ] ) ); } } build_ura(); } typedef std::vector< int > ultraregion; int diag_index( int x, int y ) { // assume x < y // compute index of diagonal x-y in ultraregion vector return num_clocks + (y-x-1 + x * (num_clocks - 1) - x * (x-1) / 2); } ultraregion get_initial() { ultraregion ur( num_clocks ); // ur.reserve( ... ); for ( int i = 0; i < num_clocks; ++i ) { for ( int j = i + 1; j < num_clocks; ++j ) { auto &diag = diagonal_guards[ j ][ i ].guards; if ( diag.size() == 0 ) ur.push_back( -1 ); else { auto it = std::lower_bound( diag.begin(), diag.end(), guard::less_eq( 0 ) ); ur.push_back( it - diag.begin() ); } } } return ur; } void output_ura( std::ostream &out ) { std::vector< ultraregion > aux( dbm_rep.size() ); for ( auto &x : visited ) { if ( x.second != -1 ) aux[ x.second ] = x.first; } for ( int i = 0; i < int( aux.size() ); ++i ) { out << "----- " << i << " -----\n"; dump_ultraregion( aux[ i ], out ); } out << "---------------\n"; for ( auto &t : transitions ) { int from, to; unsigned c; std::tie( from, c, to ) = t; out << "\t" << from << " -> " << to << " ("; if ( c == 0U ) out << "delay"; else { out << "reset"; for ( int x = 0; x < num_clocks; ++x ) { if ( c & (1U << x) ) out << ' ' << x; } } out << ")\n"; } } void dump_ultraregion( const ultraregion &ur, std::ostream &out ) { int k = 0; for ( int i = 0; i < num_clocks; ++i, ++k ) { out << i << " in "; clock_guards[ i ].output_interval( ur[ k ], out ); out << std::endl; } for ( int i = 0; i < num_clocks; ++i ) { for ( int j = i + 1; j < num_clocks; ++j, ++k ) { if ( ur[ k ] == -1 ) continue; // no constraint out << i << '-' << j << " in "; diagonal_guards[ j ][ i ].output_interval( ur[ k ], out ); out << std::endl; } } } void dump_partitions( std::ostream &out ) { for ( int i = 0; i < num_clocks; ++i ) { out << i << ": " << clock_guards[ i ] << std::endl; } for ( int i = 0; i < num_clocks; ++i ) { for ( int j = i + 1; j < num_clocks; ++j ) { out << i << '-' << j << ": " << diagonal_guards[ j ][ i ] << std::endl; } } } // --- successor region computation --- // preorder < on clocks "leaves its interval sooner than" // returns -1 if x < y, 0 if x ~ y, 1 if x > y // only applicable to bounded clocks int sooner_preorder( const ultraregion &ur, int x, int y ) { int ux = ur[ x ]; int uy = ur[ y ]; int uxy = ur[ diag_index( x, y ) ]; // endpoints auto &ex = clock_guards[ x ].guards[ ux ]; auto &ey = clock_guards[ y ].guards[ uy ]; int diff = ex.val - ey.val; int cmp = diagonal_guards[ y ][ x ].compare( diff, uxy ); if ( cmp == 0 ) { // diff in U_xy return ( ex.open == ey.open ? 0 : ex.open ? -1 : 1 ); // clocks in open intervals leave them sooner } else { return cmp; } } // find the minimal elements in the `sooner' preorder std::vector< int > find_soonest_clocks( const ultraregion &ur ) { std::vector< int > minimal; for ( int i = 0; i < num_clocks; ++i ) { // ignore unbounded clocks if ( ur[ i ] == clock_guards[ i ].last() ) continue; if ( minimal.empty() ) { minimal.push_back( i ); continue; } // compare arbitrary representant of current minimal // vector of clocks with the new candidate int cmp = sooner_preorder( ur, minimal.front(), i ); if ( cmp == 0 ) { // they are equal minimal.push_back( i ); } else if ( cmp == 1 ) { // throw away minimal, i is less than all of them minimal.clear(); minimal.push_back( i ); } } return minimal; } ultraregion get_succ( ultraregion ur ) { auto to_move = find_soonest_clocks( ur ); for ( int x : to_move ) { ++ur[ x ]; // advance the clock to next interval if ( ur[ x ] == clock_guards[ x ].last() ) { // x has become unbounded // throw away all diagonal constraints referencing x for ( int i = 0; i < num_clocks; ++i ) { if ( i == x ) continue; int l, r; std::tie( l, r ) = std::minmax( i, x ); ur[ diag_index( l, r ) ] = -1; // -1 represents no constraints } } } return ur; } // ------------------------------------ dbm ur_to_dbm( const ultraregion &ur ) { dbm ret( num_clocks + 1 ); int k = 0; for ( int i = 0; i < num_clocks; ++i, ++k ) { std::tie( ret( 0, i+1 ), ret( i+1, 0 ) ) = clock_guards[ i ].to_dbm_guards( ur[ k ] ); } for ( int i = 0; i < num_clocks; ++i ) { for ( int j = i + 1; j < num_clocks; ++j, ++k ) { if ( ur[ k ] == -1 ) { // no constraint ret( i+1, j+1 ) = ret( j+1, i+1 ) = guard::less_than( inf ); } else { std::tie( ret( j+1, i+1 ), ret( i+1, j+1 ) ) = diagonal_guards[ j ][ i ].to_dbm_guards( ur[ k ] ); } } } return ret; } ::dbm::dbm_t to_uppaal_dbm( const dbm &ur_dbm ) { ::dbm::dbm_t result( num_clocks + 1 ); result.setInit(); for ( int i = 0; i < num_clocks + 1; ++i ) { for ( int j = 0; j < num_clocks + 1; ++j ) { const guard &g = ur_dbm( i, j ); if ( g.val != inf ) { constraint_t constraint( i, j, g.val, g.open ); result &= constraint; } } } return std::move( result ); } std::vector< ultraregion > reset( ultraregion ur, dbm d, const std::vector< int > &r_clocks ) { // assume d is minimized std::vector< ultraregion > ret; std::vector< bool > r( num_clocks, false ); for ( int x : r_clocks ) { d( x+1, 0 ) = guard::less_eq( 0 ); d( 0, x+1 ) = guard::less_eq( 0 ); r[ x ] = true; ur[ x ] = 0; } for ( int y = 0; y < num_clocks; ++y ) { for ( int x : r_clocks ) { if ( x != y ) { d( x+1, y+1 ) = d( 0, y+1 ); d( y+1, x+1 ) = d( y+1, 0 ); } } } // d now represents the set of valuations immediately after reset std::vector< std::pair< int, int > > diagonals; for ( int i = 0; i < num_clocks; ++i ) { for ( int j = i + 1; j < num_clocks; ++j ) { if ( ur[ i ] == clock_guards[ i ].last() || ur [ j ] == clock_guards[ j ].last() ) { diagonals.emplace_back( -1, -1 );//no diagonal constraints } else if ( !r[ i ] && !r[ j ] ) { diagonals.emplace_back( ur[ diag_index( i, j ) ], ur [ diag_index( i, j ) ] ); // we do not change the diagonals between non-reset clocks } else { guard l = d( j+1, i+1 ); // upper bound on j - i guard u = d( i+1, j+1 ); // upper bound on i - j l.val = -l.val; // now it's lower bound on i - j l.open = ! l.open; auto &diag = diagonal_guards[ j ][ i ].guards; // find all diagonal intervals intersecting l -- u auto lb = std::upper_bound( diag.begin(), diag.end(), l ); auto ub = std::lower_bound( diag.begin(), diag.end(), u ); int li = lb - diag.begin(); int ui = ub - diag.begin(); diagonals.emplace_back( li, ui ); } } } // recursively create all ultraregions covering the dbm if ( num_clocks > 1 ) _gen_covering( diagonals, ret, ur, 0, 1, d ); return ret; } void _gen_covering( const std::vector< std::pair< int, int > > &diagonals, std::vector< ultraregion > &vec, ultraregion &tmp, int x, int y, const dbm & test_dbm ) { if ( num_clocks == 1 ) { vec.push_back( tmp ); return; } int ix = diag_index( x, y ) - num_clocks; int l = diagonals[ ix ].first; int r = diagonals[ ix ].second; ix += num_clocks; for ( int i = l; i <= r; ++i ) { tmp[ ix ] = i; // next clock pair int nx = x; int ny = y + 1; if ( ny == num_clocks ) { ++nx; ny = nx + 1; } if ( nx == num_clocks - 1 ) { // no more diagonals to consider // test whether the ultraregion has nonempty intersection // with the after-reset valuations dbm d = ur_to_dbm( tmp ); d.intersect( test_dbm ); if ( !d.empty() ) vec.push_back( tmp ); } else { _gen_covering( diagonals, vec, tmp, nx, ny, test_dbm ); } } } struct ur_hash { // this is a pretty stupid hash, but okay for now... size_t operator()( const ura::ultraregion &ur ) const { size_t ret = std::hash< int >()( ur.size() ); for ( int i : ur ) { ret ^= std::hash< int >()( i ); } return ret; } }; std::vector< dbm > dbm_rep; // dbm representation of ultraregions std::vector< ::dbm::dbm_t > uppaal_dbm_rep; std::unordered_map< ultraregion, int, ur_hash > visited; // map to index in dbm_rep // -1 means empty ur std::vector< std::tuple< int, unsigned, int > > transitions; // (a, b, c) \in transitions: a -> b via succ if c == 0 // if c > 0, a -> b via reset clocks represented by c's bit pattern decltype( transitions )::const_iterator get_first_transition( int from, unsigned reset_mask = 0 ) const { auto it = transitions.begin(); auto iE = transitions.end(); for ( ; it != iE; ++it ) { if ( std::get<0>( *it ) == from && std::get<1>( *it ) == reset_mask ) return it; } return iE; } void build_ura() { // BFS std::queue< ultraregion > Q; _visit( get_initial(), Q ); while ( !Q.empty() ) { auto u = std::move( Q.front() ); Q.pop(); int from = visited[ u ]; // (delay) succesor int to = _visit( get_succ( u ), Q ); // ASSERT to != -1 // successor should be always valid transitions.emplace_back( from, 0U, to ); // reset for ( unsigned c = 1; c < (1U << num_clocks); ++c ) { std::vector< int > r_clocks; // possibly ineffective for ( int x = 0; x < num_clocks; ++x ) { if ( c & (1U << x) ) r_clocks.push_back( x ); } auto urs = reset( u, dbm_rep[ from ], r_clocks ); // ASSERT !urs.empty() for ( auto &v : urs ) { to = _visit( std::move( v ), Q ); transitions.emplace_back( from, c, to ); } } } sort( transitions.begin(), transitions.end() ); // make it tidy } // returns the index into dbm_rep int _visit( ultraregion u, std::queue< ultraregion > &Q ) { // called whenever an ultraregion is found auto ins = visited.emplace( u, dbm_rep.size() ); if ( !ins.second ) // we already saw this one return ins.first->second; dbm d = ur_to_dbm( u ); d.minimize(); // we don't have to check if d is nonempty // all the operations ensure that we only get valid ultraregions dbm_rep.push_back( d ); uppaal_dbm_rep.push_back( to_uppaal_dbm( std::move( d ) ) ); Q.push( std::move( u ) ); return dbm_rep.size() - 1; } void statistics( std::ostream &out ) { out << "----------------------------------------" << "\n no. of ultraregions: " << dbm_rep.size() << "\n no. of transitions: " << transitions.size() << "\n----------------------------------------" << std::endl; } }; } } } #endif