Recent advances in technology, especially in data collecting and high-performance computing, are fundamentally changing how research is conducted in many fields of science. Many data collections store vast amounts of data that can only be effectively analyzed using a massively parallel data processing system. For example, the Sloan Digital Sky Survey (“SDSS”) has mapped 25% of the sky and collected over 30 TB of data (images and catalogs) on about 350 million celestial objects. SDSS has transformed astronomy research not only because its data is valuable, but also because the data has been made available to researchers via the Structured Query Language (“SQL”). Scientists have already submitted well over 20 million SQL queries to the SDSS database. There are many more examples of SQL use in science. For example, the SQLShare system, developed by the eScience institute at the University of Washington, allows scientists to upload their data (e.g., in EXCEL files) and immediately submit queries using SQL.
SQL employs a familiar select-from-where paradigm in which a SELECT clause specifies the data to be retrieved from a database, the FROM clause specifies the tables from which the data is to be retrieved, and the WHERE clause specifies predicates to restrict the data that is retrieved. The following is a sample query for the SDSS database.
This query finds the stars with a brightness between 15 and 18 in the r-band that are within 2 arc minutes of the star with a right ascension of 145.622 and a declination of 0.0346249. The SELECT clause specifies to return all of the attributes from the tables. The FROM clause specifies that the attributes from the PhotoPrimary table and the table generated by the fGetNearbyObjectEQ user-defined function (“UDF”) should be selected. The WHERE clause specifies that the result is to include stars that are in both tables and that have the specified brightness. The ORDER BY clause specifies that the objects in the result are to be ordered by brightness. Each clause has a clause type (e.g., SELECT or FROM) and one or more features (e.g., PhotoPrimary and p.objID).
By using SQL to analyze large data sets, scientists are making important scientific advances. Unfortunately, creating SQL queries remains a challenge for many scientists. Although the scientists are highly trained professionals and can easily grasp the basic select-from-where paradigm of SQL, they need to use advanced query features, including group-bys, outer-joins, user-defined functions returning tables, or spatial database operators to conduct advanced research. In addition, the scientists have to cope with complex database schemas. For example the SDSS schema has 88 tables, 51 views, 204 user-defined functions, and 3,440 columns. The learning curve for becoming an expert SQL user on a specific scientific database is steep.
Many scientists today leverage database management systems (DBMSs) only with the help of computer scientists. Alternatively, they may compose their SQL queries by sharing and reusing sample queries. For example, the SDSS website provides a selection of 57 sample queries that correspond to popular questions posed by its users. Similarly, SQLShare provides a “starter kit” of SQL queries that are translated from English questions provided by scientists. Scientists can then create complex SQL queries by “copying and pasting” from the starter kit. A drawback to using sample SQL queries is that scientists either have access to only a small sample, which may not contain the information they need, or they must search through massive logs of past queries (if available), which can be overwhelming.
Although some automated tools have been developed to help users create SQL queries, these tools generally focus on assisting the novice user. For example, several visual query building tools provide assistance to novice users who struggle with simple SQL queries and the basic select-from-where paradigm. These tools are of little help to scientists who need to develop complex SQL queries. Some tools have been proposed to cluster and rank relations in a complex schema by their importance and then to recommend important tables and attributes to SQL users. Because scientists are experts in their fields, they can quickly learn the important tables and attributes. As a result, such recommendations are a little value to scientists. Scientists do not need help with the basic select-from-where paradigm, but with advanced features—the seldom-used tables and attributes, UDFs, complex domain-specific predicates, etc.
A method and system that suggests features for inclusion in a clause of a partial query based on previously submitted queries that are similar to the partial query is provided. In some embodiments, a suggestion system receives an indication of a partial query and the selection of a clause within the partial query. For example, the partial query may be
with the WHERE clause being the selected clause. The suggestion system identifies features to suggest for inclusion in the selected clause based on an analysis of queries similar to the partial query. A query is similar to the partial query based on the features that the query has in common with the partial query. A feature is determined to be in common when both the query and the partial query have that feature in the same clause. For example, the following query has the p.objID attribute, the PhotoPrimary table, and the fGetNearbyObjEQ UDF as features in common with the partial query illustrated above. The queries, however, do not have the attribute p.r in common because the partial query has the p.r attribute in the SELECT clause and the following query has the p.r attribute in the ORDER BY clause.
The more features that a query has in common with a partial query the more similar the query is to the partial query. The suggestion system identifies the features of the similar queries that will be suggested based on the probability of that feature being in the selected clause of similar queries. For example, if 90 of the similar queries have the “p.objID=n.objID” predicate, then the suggestion system may recommend the “p.objID=n.objID” predicate before any other predicate with a count less than 90. The suggestion system thus suggests features that are likely to be relevant given the current context of a partial query (e.g., the features of the partial query) as well as queries previously submitted by users that include a similar context.
In some embodiments, to identify features that are relevant for a selected clause of a partial query, the suggestion system selects queries that are similar to the partial query and then selects popular features of those similar queries. The suggestion system identifies as similar queries those that have a feature in common with each feature of the partial query. For example, if the partial query has 10 features, then the suggestion system identifies a query as similar if it also has those 10 features. The suggestion system then identifies the popular features by counting the number of times each feature that is not already in the partial query is in a similar query. For example, if there are 100 queries that are similar to the following query and 90 of them include the “p.objID=n.objID” predicate and 20 of them include the “p.r<18 and p.r>15” predicate, then their counts would be 90 and 20, respectively. The suggestion system may represent “p.r<18” and “p.r>15” as separate features and remove the constants resulting in the features of “p.r<#” and “p.r>#.”
As a partial query becomes more complex, the number of queries that have all of the features of the partial query decreases. To ensure that sufficient features are suggested, the query system may relax the definition of what is considered similar by reducing the number of features that a query needs to have in common with the partial query. For example, if the partial query has 10 features and not enough popular features can be identified from the queries that have all 10 of those features, then the suggestion system would identify popular features of queries that have 9 features in common and continues relaxing the definition until a sufficient number of popular features has been identified.
In some embodiments, the suggestion system may use various approaches to determine whether a feature is popular. As described above, the suggestion system may simply select those features that have been used the most in similar queries. One problem with such an approach is that if a query is extremely popular, the features with the highest counts will likely be part of that query. As a result, all of the popular features will lead the user to the same extremely popular query with little chance of providing suggestions that lead to other queries. To overcome this difficulty, the suggestion system may suggest features that will likely lead the user to different queries. As the suggestion system relaxes the definition of what is similar, the suggestion system excludes any query with at least one feature that has already been included as a popular feature. As a result, such popular queries will not have the other features counted, thus giving features in not so popular queries a better chance of being considered popular and then suggested to a user.
In some embodiments, the previously submitted queries may be recorded in a query log of a database system. Since a query log may contain millions of queries, the suggestion system pre-processes the queries so that suggested features can be identified in real-time as users create queries. A query log may contain, for each query, a user identifier for the user who submitted the query, a submission time, and the query text. The suggestion system generates auxiliary tables that include a queries table, a features table and a query/features table from the query log. The queries table contains, for each query, an entry that includes the user identifier, the submission time, and the query text. The features table contains, for each feature of the query log, one entry that includes the feature and the clause that the feature is within. For example, if the PhotoPrimary table feature appears in the WHERE clause of 10 million queries, the features table will include only one entry for that feature. The query/features table maps queries to features and has, for each feature of each query, an entry that includes an identifier of a query in the queries table and an identifier of a feature in the features table.
In some embodiments, the suggestion system pre-processes the query log to eliminate queries that are not the final query of a query session. A query session is a sequence of queries written by the same user as part of a single task that generates the final query for that task. The suggestion system identifies query sessions in two phases. In the first, or segmentation, phase the suggestion system analyzes any changes between consecutive queries that have been submitted by a user to identify the beginning of a new revision cycle. A revision cycle is the process by which a user refines and resubmits a query until a desired task is complete or another task has been started. The suggestion system labels the queries from the start of one revision cycle to the start of the next as a query segment. In the second, or stitching, phase the suggestion system stitches multiple query segments together if they are part of a single, larger revision cycle. For example, when a user is having problems formulating a complex query, the user may switch to formulate a different query and later return to the complex query. The formulation of the complex query is thus separated into two query segments by the formulation of the intervening different query. The suggestion system identifies that the two query segments for the complex query represent the same query session and stitches the query segments together to form a single query session.
In some embodiments, the suggestion system may use a supervised learning algorithm to identify both the beginning of a revision cycle and which revision cycles should be stitched together to be part of the same query session. The segmentation phase employs a classifier that is trained using various segmentation features derived from pairs of time-wise adjacent queries. These segmentation features may include the time intervals between queries, a cosine similarity between the clauses that are not the same, and any relationships between abstract syntax tree (“AST”) representations of the queries. One relationship between ASTs, referred to as inclusion type, can indicate whether the queries are the same, the second query has more terms than the first query, the second query has fewer terms than the first query, the first query is a subquery of the second query, the second query is a subquery of the first query, or some other type of relationship. To train the classifier, the suggestion system extracts the segmentation features for pairs of features from a query log of training data. The pairs may be manually labeled to indicate whether they are in the same or different segments. The stitching phase may employ a classifier that is trained using the same segmentation features (except for the time interval feature) and the same labeling as that employed by the segmentation phase. The stitching phase compares the last query in each segment to the first query in each time-wise successor segment to determine whether the queries are in the same segment.
In some embodiments, the suggestion system is a middleware-layer on top of a standard relational DBMS. When users submit queries against the database, a query logger component logs these queries in a query repository. The suggestion system uses this query repository to produce SQL-autocomplete recommendations. A query eliminator component periodically prunes the query log to improve recommendation performance by shrinking the query repository.
When the query logger component logs a query, it extracts various features from the query and updates the query repository. A feature is a specific fragment of SQL such as a table name in the FROM clause (or view name or table-valued function name) or a predicate in the WHERE clause.
The query repository comprises three tables: Queries, Features, and QueryFeatures. A Queries table contains metadata about the query (e.g., id, timestamp, query text) and may be populated using an existing infrastructure for query logging such as those offered by most DBMSs. A Features table lists each feature (i.e., SQL fragment) and the clause from which it was extracted. A QueryFeatures table lists which features appear in which queries. The query repository contains the following three tables:
1. Queries (id, timestamp, user, database name, query text, running time, output size)
2. Features (id, feature description, clause)
3. QueryFeatures (query, feature)
The Queries table stores the details of each logged query and may be populated by a DBMS existing infrastructure. The Features table consists of the features (i.e., SQL fragment) that have been extracted from these queries and the clauses from which they were extracted. The feature descriptions may be parameterized if there is some constant involved (e.g., the predicate PhotoPrimary.objID=55 is translated into the parameterized predicate PhotoPrimary.objID=#). The QueryFeatures table maintains the information about which features appears in which queries. The query repository may also contain the following tables:
1. MarginalProbs(featureID, probability)
2. CondProbs(feature1, feature2, probability)
The MarginalProbs table stores the marginal probability for each feature across the whole workload. The CondProbs table contains the conditional probability of feature1 given feature2, for every pair of features that have appeared together.
In some embodiments, the suggestion system supports the following classes of features:
When a user composes a query, the user selects a clause and asks for recommendations with this clause. The goal of the suggestion system is to recommend k features that are most likely to appear in that clause in the user's intended query. To produce its recommendations, the suggestion system views the space of queries as a directed acyclic graph (“DAG”) such as that shown in
The query that the user intends to write is somewhere below the current vertex in the DAG, but the suggestion system does not know which query it is. It approximates the intended query with the set of all queries in the query repository that are descendants of the current vertex in the DAG. Such queries are referred to as the potential goals for the partial query. The suggestion system may assume that the set is not empty. Given this set of potential goals, there are several ways to rank the features that could possibly be added to the user query. One way is to recommend the most popular features among all those queries. A problem that arises is that this can result in k recommendations that all lead to a single, extremely popular query. An alternate method is to recommend k features that cover a maximum number of queries in the potential goals set.
The problem of selecting features for recommendation and some solutions are more formally described below.
DEFINITION 1. A feature f is a function that takes a query as input and returns true or false depending on whether a certain property holds on that query. Some examples are fPhotoPrimaryFROM that represents whether the PhotoPrimary table appears in the query's FROM clause, fPhotoPrimary.objID=Neighbors.objIDFROM that represent whether the predicate PhotoPrimary.objID=Neighbors.objID appears in the WHERE clause, or fdistinct that represents whether the keyword “distinct” appears anywhere in the query. A feature can also have a clause associated with it, denoted clause (f). For example, clause(fPhotoPrimaryFROM)=FROM. The notation fsc denotes the feature that string s appears in clause c.
DEFINITION 2. The feature set of a query q, is defined as:
features(q)={f|f(q)=true}
When the suggestion system recommends a snippet or feature, it is recommending that the user modify the query so that the snippet evaluates as true for the query. For example, when it recommends fPhotoPrimaryFROM, it is recommending that the user add PhotoPrimary to the FROM clause.
DEFINITION 3. The dependencies of a feature f, dependencies (f), is the set of features that must be in the query so that no syntactic error is raised when one adds f (e.g., dependencies (fPhotoPrimary.objID=Neighbors.objIDWHERE)={fPhotoPrimaryFROM, fNeighborsFROM}). The suggestion system only suggests a feature f for a partial query q if dependencies(f)⊂features(q). In the DAG, feature sets have parent-child relationships defined as follows:
DEFINITION 4. A feature set F2 is a successor of a feature set F1, if ∃ f where F2=F1∪{f} and dependencies (f)⊂F1.
A successor of a feature set F1 is thus a feature set F2 that can be reached by adding a single, valid feature. Additionally, recommendations are based on feature popularity that is captured by either marginal or conditional probabilities.
DEFINITION 5. Within a workload W, the marginal probability of a set of features F is defined as
(i.e., the fraction of queries which are supersets of F). As shorthand, P(Q) represents P(features(Q)), and P(f) represents P({f}).
DEFINITION 6. The conditional probability of a feature f given a feature set F is defined as
DEFINITION 7. The workload DAG T=(V,E,ω,χ) for a query workload W is constructed as follows:
Every syntactically correct partial query appears in the workload DAG since there is a vertex for every valid subset of F. Given a partial query and its vertex q, the goal of the suggestion system is to lead the user toward his or her intended query q* (also a vertex in the DAG), one snippet or feature at a time. The suggestion system assumes that there is a path from q to q* that is the user can reach q* by adding snippets to their query. Since the suggestion system suggests one snippet at a time, the recommendation problem becomes that of ranking the outgoing edges of q. Recommending an edge e corresponds to recommending addlFeature(e). In one example a user has written SELECT*FROM PhotoPrimary. This query is positioned at vertex v in
The suggestion system recommends suggestions based on the current context (i.e., the current partial query). In the following example, a definition of the problem that is to be solved is provided.
DEFINITION 8. Given a workload DAG and a vertex q, define potential_goals (q)={v|v is shaded and reachable from q}.
The potential_goals of q is the set of queries that could potentially be the user's intended query, if it appears in the workload. Sometimes, potential_goals (q) is the empty set.
Given a workload DAG G and a partial query q, the goal of two embodiments is to recommend a set of k outgoing edges, e1, . . . , ek, from q that:
1. Max-Accuracy Problem: maximizes
2. Max-Query-Coverage Problem: maximizes
Continuing with the example, assume the suggestion system recommends the top 2 snippets to add to the FROM clause. If the goal is Max-Accuracy, then it suggests SO and PO. This corresponds to the two outgoing edges from q, with the highest conditional probabilities. If the goal is Max-Query-Coverage, then it suggests SO and fGN. If the user's intention is the rightmost shaded query, then suggesting SO covers this case. If the user's intention is not that query, then rather than PO, it is better to suggest fGN because it increases the number of potential_goals covered.
In general, it is infeasible to build the workload DAG as it can have up to 2n vertices, where n=|F|. Thus, the suggestion system implements two algorithms that simulate traversing parts of the DAG without ever constructing it: SSAccuracy and SSCoverage.
Given a partial query q and a query workload W, the goal of the SSAccuracy algorithm is to suggest the k features with the highest conditional probabilities given q. If q's features have previously appeared together in past queries, SSAccuracy is able to efficiently identify the features with the highest conditional probabilities with a single SQL query over the QueryFeatures table, as shown in Table 1. The SQL query in Table 1 finds the most popular features among queries that share m features with partial query q. The NOT EXISTS clause is included for SSCoverage, but omitted for SSAccuracy.
By setting m to |features(q)|, the first half of the query finds potential_goals(q), that is the queries that have all the features of q. It then orders all the features that appear in these SimilarQueries by their frequencies within this set of queries. Each qf.feature f corresponds to one outgoing edge from q (i.e., the edge e where addlFeature(e)=f). Additionally, if count(s.query) is divided by |potential_goals(q)|, the result would be P(f|q). Thus, this query returns a list of edges, ordered by weight (i.e., the conditional probability of the feature given q).
Every partial query q appears in the DAG, but it can happen that all incoming edges have weight 0, and all outgoing edges are unknown. This happens when potential_goals(q)=. This occurs, for example, if the user has written q=SELECT FROM SpecObjAll, fGetNearbyObjEq(143.6,0.021,3) and requests suggestions in the FROM clause (which is represented by vertex u in
The suggestion system traverses up the DAG from q until it reaches the vertices whose marginal probability is not zero (i.e., there exists an incoming edge with weight>0). This allows the system to find the largest subsets of features(q) that appear in the workload. In the above example, the suggestion system traverses up to the vertices {SO} and {fGN}. The suggestion system suggests the most popular features among the queries under these vertices. This can be achieved by executing the SQL query shown in Table 1. First, the suggestion system sets m to |features(q)|, thus looking at potential_goals (q). If fewer than k features are returned, it sets m to |features(q)|−1, thus considering queries that share |features(q)|−1 features with q. It repeatedly decrements m until k features are returned. The suggestion system executes the query in Table 1 at most |features(q)| times. In other words, the suggestion system will not iterate through every subset of features(q). Instead, it considers all subsets of size m all at once, and it does this for m=n, n−1, n−2, . . . , 0, where n=|features(q)|.
This process can cause some ambiguity as to how to rank features. For example, if P(f1|{SO,fGN})=0.8 and P(f2|{SO})=0.9, it is not clear whether f1 or f2 should be ranked first. Heuristically, the suggestion system picks f1, because it ranks recommendations based on more similar queries first. Algorithm 1 outlines the full SSAccuracy algorithm (if passed t=SSAcc).
In some embodiments, the suggestion system uses two relations to improve the SSAccuracy algorithm's recommendation time. The first is MarginalProbs, which contains P(f) for every feature f. When the user's partial query q is the empty query (i.e., features(q)=0), or if q consists of only features that have never before appeared in the workload, the suggestion system executes an order by query over MarginalProbs, instead of the more complex SQL in Table 1. (When q contains only unseen features, the suggestion system traverses up to the root vertex since it is the largest subset of q that appears in the workload. So, the suggestion system makes its suggestions for 0, and thus exploits MarginalProbs.)
The second is CondProbs, which contains the conditional probability P(f1|f2) for every pair of features f1, f2. It is indexed on the f2 column. It is leveraged when the user's partial query q contains just one feature f, or it contains multiple features, but only one feature f has appeared in the workload. In these cases, the suggestion system can execute a simple query over CondProbs with filter f2=f, and order by the conditional probability, instead of executing the slower SQL in Table 1.
The Max-Query-Coverage problem suggests the features f1, . . . , fk that maximize the probability of at least one suggestion being helpful. The goal is to diversify the suggestions, in order to avoid making suggestions that all lead toward the same query. It turns out that the problem is NP-hard. Instead of an exact solution, in some embodiments, the suggestion system uses an approximation algorithm, SSCoverage, which is a greedy, approximation algorithm for Max-Query-Coverage. Given a set of elements U and a set of sets S=S1, . . . , Sn, where each SiεU, the Maximum Coverage problem is to find a subset of S of size k (fixed) such that a maximum number of elements in U are “covered.”
The SSCoverage algorithm proceeds as follows. To compute the first recommendation f1, it executes the SQL query in Table 1, with the NOT EXISTS clause and previous ←. This is equivalent to SSAccuracy's first recommendation because the Max-Accuracy and Max-Query-Coverage formulas are equivalent when k=1. For its second suggestion, SSCoverage executes the Table 1 query again, but with previous ←{f1}. This effectively removes all the queries covered by f1 (i.e., potential_goals(q∪{f1})), and finds the feature with the highest coverage (i.e., conditional probability) in the remaining set. In terms of the workload DAG, this step discards the whole subgraph rooted at f1, and then finds the best feature among the remaining DAG. It repeats this process k times in order to collect k features. Algorithm 1 describes SSCoverage in detail (if passed t=SSCov).
As discussed above, the suggestion system extracts query sessions from the query log and then eliminates all queries except those that appear at the end of a session. The goal of the segmentation phase is to take a pair of consecutive queries P, Q, and decide whether the two queries belong to the same query session or not. The algorithm proceeds in three steps. First, it constructs the AST for each query and transforms it into canonical form, by, for example, removing any constants, and alphabetically ordering the list of tables in the FROM clause (this process is also used by the query logger component). Second, it extracts a set of segmentation features from P and Q as well as extra information such as timestamps of queries and the query output of the preceding query P. Unlike the features used for recommendations, the segmentation features capture the differences between two queries. Some examples include the time interval between the queries, the cosine similarities between their different clauses, and the relationship between ASTs. In the third step, using these segmentation features, the suggestion system may use a perception-based classification algorithm to decide whether the queries belong in the same segment. For this final step, as training data, the suggestion system requires some labeled data in the form of queries labeled with the identifier associated with the task that the queries are intended for.
A significant segmentation feature is AST inclusion type. This feature represents whether the relationship between the two queries' ASTs is the Same, Add, Delete, Merge, Extract or None. This feature is based on the following observations. Within a query segment, a user incrementally adds or removes terms from the query after seeing the query result of the previous query. Such incremental edits are captured by the values Same, Add, or Delete. Occasionally, the user may introduce a subquery written in the past or copied from some sample query to compose a more complex query. The user may also pull out a subquery to debug or analyze unexpected results. In both these cases, the user may start to work toward a different purpose when the change involves subqueries; this signals a new query segment. Merge and Extract captures such changes that involve subqueries. Table 2 summarizes these five AST inclusion types. Each feature value in Table 2 has an expected decision on segmentation that is discussed in the text. P and Q denote the preceding query and the following query, respectively.
Although the AST inclusion type may be a strong indicator of continuing or breaking a query segment, it does not capture the amount of change. Thus, the other segmentation features, which capture the degree of change, help improve accuracy.
Query segments are useful because of the coherency among the queries in the same segment. However, in order to extract more complete query sessions, the suggestion system stitches together multiple segments.
The stitching phase tests whether two segments create a single revision cycle, smooth transitions with small changes, when they are concatenated in time order. To perform stitching, the suggestion system iterates over each segment s in the input and all its time-wise successor segments. It runs the core of the segmentation algorithm between the last query of s and the first query of a time-wise successor segment t with a modification. Since the suggestion system is now considering segments that are separated by multiple segments, the time interval between the queries becomes less meaningful. Thus, the algorithm treats the time interval as a missing attribute. Then, if the segmentation algorithm outputs that the last query of s and the first query of t belong in the same segment and this does not contradict the value of AST inclusion type, the suggestion system concatenates the two segments.
The processor on which the suggestion system may be implemented may include a central processing unit and local memory and may include input devices (e.g., keyboard and pointing devices), output devices (e.g., display devices), and storage devices (e.g., disk drives). The processors may access computer-readable media that includes computer-readable storage media and data transmission media. The computer-readable storage media includes memory and other storage devices that may have recorded upon or may be encoded with computer-executable instructions or logic that implements the suggestion system. The data transmission media is media for transmitting data using signals or carrier waves (e.g., electromagnetism) via a wire or wireless connection. Various functions of the suggestion system may also be implemented on devices using discrete logic or logic embedded as an application-specific integrated circuit. The devices on which the suggestion system is implemented are computing devices.
The suggestion system may be described in the general context of computer-executable instructions, such as program modules, executed by one or more computers, processors, or other devices. Generally, program modules include routines, programs, objects, components, data structures, and so on, that perform particular tasks or implement particular data types. Typically, the functionality of the program modules may be combined or distributed as desired in various embodiments.
Although the subject matter has been described in language specific to structural features and/or acts, it is to be understood that the subject matter defined in the appended claims is not necessarily limited to the specific features or acts described above. Rather, the specific features and acts described above are disclosed as example forms of implementing the claims. For example, in some embodiments, the suggestion system may provide suggestions of feature for inclusion in a query without the user having to select a specific clause. As the user enters the partial query, the suggestion system may provide suggestions for only the clause that the user is currently enter or for all the clauses of the partial query. Accordingly, the invention is not limited except as by the appended claims.
This application claims the benefit of U.S. Provisional Patent Application No. 61/541,482 filed Sep. 30, 2011, which is incorporated herein by reference in its entirety.
This invention was made with government support under IIS-0845397, IIS-0713576, CNS-0454425, and IIS-0627585, each awarded by the National Science Foundation. The government has certain rights in the invention.
Number | Date | Country | |
---|---|---|---|
61541482 | Sep 2011 | US |