#include <bits/stdc++.h>
using namespace std;
using lint     = long long;
const lint inf = 1LL << 60;
const lint mod = 1000000007;

struct mint {
    lint v;
    lint _mod;
    mint() : v(0) {}
    mint(signed v, lint _mod = mod) : v(v), _mod(_mod) {}
    mint(lint t, lint _mod = mod) : _mod(_mod) {
        v = t % _mod;
        if (v < 0)
            v += _mod;
    }

    mint pow(lint k) {
        mint res(1), tmp(v);
        while (k) {
            if (k & 1)
                res *= tmp;
            tmp *= tmp;
            k >>= 1;
        }
        return res;
    }
    static mint add_identity() { return mint(0); }
    static mint mul_identity() { return mint(1); }
    mint inv() { return pow(_mod - 2); }

    mint &operator+=(mint a) {
        v += a.v;
        if (v >= _mod)
            v -= _mod;
        return *this;
    }
    mint &operator-=(mint a) {
        v += _mod - a.v;
        if (v >= _mod)
            v -= _mod;
        return *this;
    }
    mint &operator*=(mint a) {
        v = v * a.v % _mod;
        return *this;
    }
    mint &operator/=(mint a) { return (*this) *= a.inv(); }

    mint operator+(mint a) const { return mint(v) += a; };
    mint operator-(mint a) const { return mint(v) -= a; };
    mint operator*(mint a) const { return mint(v) *= a; };
    mint operator/(mint a) const { return mint(v) /= a; };

    mint operator-() const { return v ? mint(_mod - v) : mint(v); }

    bool operator==(const mint a) const { return v == a.v; }
    bool operator!=(const mint a) const { return v != a.v; }
    bool operator<(const mint a) const { return v < a.v; }
};
ostream &operator<<(ostream &os, mint m) { return os << m.v; }

// 0-indexed bottom up Segment Tree
// UNIT is the identity element of operation func
template <typename T = int>
struct SegmentTree {
    using F = function<T(T, T)>;
    int n;
    vector<T> dat;
    F func;
    T UNIT;

    SegmentTree(int n_, F func_, T UNIT_) : func(func_), UNIT(UNIT_) {
        n = 1;
        // full binary tree: num of leaves = n = 2^k >= n_
        while (n < n_)
            n *= 2;
        dat.assign(2 * n - 1, UNIT);
    }
    SegmentTree(vector<T> v_, F func_, T UNIT_) : func(func_), UNIT(UNIT_) {
        n      = 1;
        int nv = v_.size();
        while (n < nv)
            n *= 2;
        dat.assign(2 * n - 1, UNIT);
        for (int i = 0; i < nv; ++i) {
            dat[n - 1 + i] = v_[i];
        }
        for (int i = n - 2; i >= 0; --i) {
            dat[i] = func(dat[2 * i + 1], dat[2 * i + 2]);
        }
    }
    void update(int k, T a) {
        // leaves are at index n-1 to 2*n-2
        k += n - 1;
        dat[k] = a;
        while (k > 0) {
            // k -> parent node
            k = (k - 1) / 2;
            // func(child nodes)
            dat[k] = func(dat[2 * k + 1], dat[2 * k + 2]);
        }
    }
    // get result of func() in [l, r)
    T query(int l, int r) {
        l += n - 1;
        r += n - 1;
        T retl = UNIT, retr = UNIT;
        while (l < r) {
            if ((l & 1) == 0)
                retl = func(retl, dat[l]);
            if ((r & 1) == 0)
                retr = func(dat[r - 1], retr);
            l = l / 2;
            r = (r - 1) / 2;
        }
        return func(retl, retr);
    }
};

template <typename T = lint>
class HLDecomposition {
    using F = function<T(T, T)>;
    void dfs_sz(int v) {
        for (int &u : edges[v])
            if (u == par[v])
                swap(u, edges[v].back());
        if (~par[v])
            edges[v].pop_back();

        for (int &u : edges[v]) {
            par[u] = v;
            dep[u] = dep[v] + 1;
            dfs_sz(u);
            sub[v] += sub[u];
            if (sub[u] > sub[edges[v][0]])
                swap(u, edges[v][0]);
        }
    }
    void dfs_hld(int v, int c, int &pos) {
        vid[v]      = pos++;
        inv[vid[v]] = v;
        type[v]     = c;
        for (int u : edges[v]) {
            if (u == par[v])
                continue;
            head[u] = (u == edges[v][0] ? head[v] : u);
            dfs_hld(u, c, pos);
        }
        out[v] = pos;
    }
    bool built;

  public:
    vector<vector<int>> edges;
    vector<int> vid, head, sub, par, dep, inv, type, out;
    F f;
    T unit;
    SegmentTree<T> segTree;

    HLDecomposition(int n, F f, T unit)
        : built(false), edges(n), vid(n, -1), head(n), sub(n, 1), par(n, -1), dep(n, 0), inv(n), type(n), out(n), f(f),
          unit(unit), segTree(n, f, unit) {}

    void add_edge(int u, int v) {
        edges[u].emplace_back(v);
        edges[v].emplace_back(u);
    }

    void build(vector<int> rs = {0}) {
        int c = 0, pos = 0;
        for (int r : rs) {
            dfs_sz(r);
            head[r] = r;
            dfs_hld(r, c++, pos);
        }
        built = true;
    }

    void assign(vector<T> nodes) {
        assert(nodes.size() == vid.size());
        vector<T> v(nodes.size());
        for (int i = 0; i < (int)nodes.size(); ++i) {
            v[vid[i]] = nodes[i];
        }
        segTree = SegmentTree<T>(v, f, unit);
    }

    int lca(int u, int v) {
        assert(built);
        while (1) {
            if (vid[u] > vid[v])
                swap(u, v);
            if (head[u] == head[v])
                return u;
            v = par[head[v]];
        }
    }

    int distance(int u, int v) { return dep[u] + dep[v] - 2 * dep[lca(u, v)]; }

    void update(int k, T x) { segTree.update(vid[k], x); }

    // query for each vertex
    // [l, r] <- attention!! closed section (path) on the tree
    T query(int u, int v) {
        assert(built);
        T ret = unit;
        while (1) {
            if (vid[u] > vid[v])
                swap(u, v);
            ret = f(ret, segTree.query(max(vid[u], vid[head[v]]), vid[v] + 1));
            if (head[u] != head[v])
                v = par[head[v]];
            else
                return ret;
        }
    }

    // query for subtree whose root is u
    T query_subtree(int u) {
        assert(built);
        return segTree.query(vid[u], out[u]);
    }

    // query for each edge: exclude lca's value
    // [l, r] <- attention!! closed section (path) on the tree
    T query_edge(int u, int v) {
        assert(built);
        T ret = unit;
        while (1) {
            if (vid[u] > vid[v])
                swap(u, v);
            if (head[u] != head[v]) {
                ret = f(ret, segTree.query(vid[head[v]], vid[v] + 1));
                v   = par[head[v]];
            } else {
                if (u != v)
                    ret = f(ret, segTree.query(vid[u] + 1, vid[v] + 1));
                return ret;
            }
        }
    }
};

struct UnionFind {
    vector<int> rank, parent, size;

    // +1 for 1-indexed nodes
    UnionFind(int n) : rank(n + 1, 0), parent(n + 1), size(n + 1, 1) {
        iota(parent.begin(), parent.end(), 0); // parent is itself
    }
    int root(int x) {
        if (x != parent[x]) {
            parent[x] = root(parent[x]);
        }
        return parent[x];
    }
    bool isSame(int x, int y) { return root(x) == root(y); }
    bool unite(int x, int y) { return link(root(x), root(y)); }
    bool link(int x, int y) {
        if (x == y)
            return false;
        if (rank[x] > rank[y]) {
            parent[y] = x;
            size[x] += size[y];
        } else {
            parent[x] = y;
            size[y] += size[x];
            if (rank[x] == rank[y]) {
                rank[y]++;
            }
        }
        return true;
    }
    int getSize(int x) { return size[root(x)]; }
};

// Kruskal algorithm: build minimum spanning tree
template <typename T>
struct Kruskal {
    struct edge {
        int from, to, used;
        T cost;
        edge(int from_, int to_, T cost_) : from(from_), to(to_), used(0), cost(cost_) {}
        bool operator<(const edge &e) const { return cost < e.cost; }
    };
    vector<edge> es;
    UnionFind uf;
    Kruskal(int n) : uf(n + 1) {}
    void addEdge(int f, int t, T c) { es.emplace_back(f, t, c); }
    T build() {
        sort(es.begin(), es.end());
        T res = 0;
        for (auto &e : es) {
            if (!uf.isSame(e.from, e.to)) {
                res += e.cost;
                uf.unite(e.from, e.to);
                e.used = 1;
            }
        }
        return res;
    }
};

int main() {
    cin.tie(nullptr);
    ios::sync_with_stdio(false);
    int n, m;
    cin >> n >> m;
    lint x;
    cin >> x;
    Kruskal<lint> kr(n);
    for (int i = 0; i < m; ++i) {
        int u, v, w;
        cin >> u >> v >> w;
        u--, v--;
        kr.addEdge(u, v, w);
    }
    lint mincost = kr.build();
    vector<vector<int>> edges(n);
    vector<vector<pair<int, lint>>> wedges(n);
    for (auto &p : kr.es) {
        if (p.used) {
            edges[p.from].emplace_back(p.to);
            edges[p.to].emplace_back(p.from);
            wedges[p.from].emplace_back(p.to, p.cost);
            wedges[p.to].emplace_back(p.from, p.cost);
        }
    }
    vector<lint> cost(n, 0);
    function<void(int, int)> dfs = [&](int c, int p) {
        for (auto &v : wedges[c]) {
            if (v.first == p)
                continue;
            cost[v.first] = v.second;
            dfs(v.first, c);
        }
    };
    dfs(0, -1);
    auto f          = [](lint l, lint r) { return max(l, r); };
    const lint unit = -inf;
    HLDecomposition<lint> hld(n, f, unit);
    hld.edges = edges;
    hld.build();
    hld.assign(cost);
    int le = 0, eq = 0, gr = 0;
    for (auto &p : kr.es) {
        if (!p.used) {
            lint adds = p.cost - hld.query_edge(p.from, p.to);
            if (adds < x - mincost)
                le++;
            else if (adds == x - mincost)
                eq++;
            else
                gr++;
        }
    }
    mint ret = 0;
    if (x < mincost) {
        cout << 0 << "\n";
        return 0;
    }
    if (x == mincost) {
        ret += (mint(2).pow(n - 1) - 2) * mint(2).pow(m - n + 1);
    }
    ret += mint(2) * (mint(2).pow(eq) - 1) * mint(2).pow(gr);
    cout << ret << "\n";
    return 0;
}

Measured memory_bytes: 640