From 606a81d3893a85dcc2c824e659ed04e44b929310 Mon Sep 17 00:00:00 2001 From: Evgeny Skvortsov <71790359+EvgSkv@users.noreply.github.com> Date: Tue, 16 May 2023 20:45:20 -0700 Subject: [PATCH] Updating documentation. --- .DS_Store | Bin 0 -> 6148 bytes docs/github-mark.png | Bin 0 -> 6393 bytes docs/index_new.html | 473 ++++++++++++++++++++++++++++++++++++ docs/logica_logo.png | Bin 0 -> 44280 bytes docs/logica_syntax.css | 57 +++++ docs/sandbox.html | 312 ++++++++++++++++++++++++ docs/syntax_highlighting.js | 69 ++++++ 7 files changed, 911 insertions(+) create mode 100644 .DS_Store create mode 100644 docs/github-mark.png create mode 100644 docs/index_new.html create mode 100644 docs/logica_logo.png create mode 100644 docs/logica_syntax.css create mode 100644 docs/sandbox.html create mode 100644 docs/syntax_highlighting.js diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..cd4f9320b87f7708ae4f28de2cf1e8f9317135b4 GIT binary patch literal 6148 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 Hu0mjfZ$b4` literal 0 HcmV?d00001 diff --git a/docs/index_new.html b/docs/index_new.html new file mode 100644 index 0000000..ce269e8 --- /dev/null +++ b/docs/index_new.html @@ -0,0 +1,473 @@ + + + + + Logica: Modern Logic Programming + + + + + + + +
+ + + +
+ Logic Programming Language
for Data Analysis + + + +
+
+ +
+ +
+ +
+ View project on
+ GitHub. +
+
+
+ +

What is Logica?

+ +
+

+ Logica is an open source declarative logic programming language for data manipulation. +

+ +

+ Logica extends syntax of logic programming for intuitive and efficient data + manipulation. It compiles to SQL thus providing you access to the power + of SQL engines with the convenience of logic programming syntax. +

+
+ + +

Examples

+ + +

+ One may say that for programming languages like Python and Java functions are the + basic building blocks. For Logica and other logic programming languages + those building blocks are predicates. + + Logic program is defined as a set of rules that define output predicates + from pre-defied predicates. Those pre-defined predecates represent input data. + + For example here is a rule to identify names of expensive books, from an existing + table of book prices. +

+ + +
+# Logica rule to get expensive books. +ExpensiveBook(book_name) :- # book_name is expensive if and only if + Book(book_name, price), # book_name costs price + price > 100; # and price is greater than 100. +
+ +

+If you are familiar with SQL, you may see that the rule above +is equivalent to the flowing SQL statement. Not that familiarity with SQL is +required to learn Logica, not at all. +

+ +
+# SQL statement to get expensive books. +SELECT book_name +FROM book +WHERE price > 100; +
+ +Predicate is a statement with variables. Any table can be treated as predicate, +where column names are the variables, and each row is a set of values of the variables +that satisfies the statement. + +While SQL is quite convenient for small queries like the one above it +gets hard to read when complexity grows. Logica leverages power of +mathematical syntax to scale nicely as complexity grows. + +Let's assume we have a table BabyNames +that for each name, year, city and +gender specifies +number of babies of that name born. +The following program finds a list of popular names, where a name +is defined as popular if it was the most popular name on some year. + +
+# Count babies per year. +NameCountByYear(name:, year:) += number :- + BabyNames(name:, year:, number:); + +# For each year pick the most popular. +TopNameByYear(year) ArgMax= name -> NameCountByYear(name:, year:); + +# Accumulate most popular name into a table, droppig the year. +PopularName(name: TopNameByYear()); +
+ +Sometimes data analysis requires solving algorithmic problems. Logica's +syntax is suited for it naturally. Here is a program finding prime numbers +that are less than 100. + +
+# Define natural numbers from 0 to 99. +N(x) :- x in Range(100); +# Define primes. +Prime(prime: x) :- + N(x), + x > 1, + ~( + N(y), + y > 1, + y != x, + x % y == 0 + ); +
+ +

+Finally here is an example of program that runs over +GDELT Project dataset, +finding people mentioned in the context of "artificial general intelligence". +

+ + +

Observe that program is divided into a rule defining predicate + NewsData and rule for AgiMentions. +The first rule is essentially doing data cleaning, formatting the dataset in a shape +that is convenient to use. Then second rule peforms the task at hand. +

+ +

+In Logica problems are naturally split into smaller components that end up +reusable. So in the future if we have more analysis to do with GDELT dataset +we may take advantage of the NewsData predicate that we just wrote. +

+ +
+# Structuring the data conveniently. +NewsData(year:, month:, day:, persons:, quotations:) :- + gdelt-bq.gdeltv2.gkg(persons: persons_str, quotations:, date: date_num), + # Column `data` in GDELT dataset is given as an integer. + year == ToInt64(Substr(ToString(date_num), 1, 4)), + month == ToInt64(Substr(ToString(date_num), 5, 2)), + day == ToInt64(Substr(ToString(date_num), 7, 2)), + persons List= (person :- person in Split(persons_str, ";")); + +# Performing the task at hand. +@OrderBy(AgiMentions, "mentions desc"); +@Limit(AgiMentions, 10); +AgiMentions(person:, mentions? += 1) distinct :- + person in persons, + Like(quotations, "%artificial general intelligence%"), + NewsData(persons:, quotations:); + +
+ +This program completes in interactive time +when ran over the 4TB dataset +via BigQuery. + +
+

Why Logica?

+ +Logica is for engineers, data scientists and other specialists who +need to perform complex data processing and analysis. +Queries and pipelines written in Logica can run on +BigQuery and SQLite engines. +Information stored in these systems is thus available in Logica. + + +

+Logica compiles to SQL and gives you access to the power of SQL engines, +including the massively distrbuted Google BigQuery engine, +with the convenience of logic programming syntax. This is useful because +BigQuery is magnitudes more powerful than state of the art native +logic programming engines. +

+ +

+We encourage you to try Logica, especially if +

+ +
    +
  • you already use logic programming and need more computational power, or
  • +
  • you use SQL, but feel unsatisfied about its readability, or
  • +
  • you want to learn logic programming and apply it to processing of Big Data.
  • +
+ +Among other engines, there is partial support for Trino and Postgres. +Contributions to improve this support are +very welcome! + +
+ +

I have not heard of logic programming. What is it?

+ +

+Logic programming is a declarative programming paradigm where the program is +written as a set of logical statements. +

+ +

+Logic programming was developed in academia from the late 60s. Prolog and +Datalog are the most prominent examples of logic programming languages. +Logica is a successor to +Yedalog, +a language created at Google earlier. Logica as well as Yedalog belong to +Datalog family. +

+ +

+Datalog and relational databases start from the same idea: think of data +as relations and think of data manipulation as a sequence of operations over +these relations. But Datalog and SQL differ in how these operations are +described. Datalog is inspired by the mathematical syntax of the first order +propositional logic and SQL follows the syntax of natural language. +

+ +

+SQL was based on the natural language to give access to databases to the people +without formal training in computer programming or mathematics. This convenience +may become costly when the logic that you want to express is non trivial. +There are many examples of hard-to-read SQL queries that correspond to simple +logic programs. +

+ +

+Logica follows Yedalog in the attempt to merge these branches back together: +extending the elegant syntax of Logic Programming to solve practical problems +and leverage the tremendous advances of SQL infrastructure for the execution. +

+ + +

How does Logica work?

+ +Logica compiles the logic program into a SQL expression, +so it can be executed on BigQuery, the state of the art SQL engine. + +Among database theoreticians Datalog and SQL are known to be equivalent. +And indeed the conversion from Datalog to SQL and back is often straightforward. +However there are a few nuances, for example how to treat disjunction and negation. In Logica we tried to make choices that make understanding of the resulting SQL structure as easy as possible, thus empowering user to write programs that are executed efficiently. + +

Why is it called Logica?

+ +Logica stands for Logic with aggregation. + +
+

How to learn?

+ + +
+ 🏖️ Playground +
+
+ + +
+ 🎓 Tutorial +
+
+ +
+ +

+Learn basics of Logica with the +CoLab tutorial +located at tutorial folder. +See examples of using Logica in examples folder. + +You try Logica immediately in the browser in Playground. +

+ +

+It is easy to install Logica on your machine as well. +

+ +

Installation

+ +Install Logica with `pip`. + +
+# Install: +$ python3 -m pip install logica +# Run: +$ python3 -m logica +# (optional) Create alias for convenience: +alias logica=python3 -m logica +
+ +Let's say this program is written in file + hello.l. +
+@Engine("sqlite"); +Greeting("Hello world!"); +
+ +When exectued with + +
+$ logica hello.l run Greeting +
+ +it should produce the following table:
+ +
++--------------+ +| col0 | ++--------------+ +| Hello world! | ++--------------+ +
+ + +

Join the discussion!

+ +If you have any questions or ideas about Logica, you +are welcome to post those in Discussions section of the repo! + + + +
+

+Unless otherwise noted, the Logica source files are distributed under the Apache 2.0 license found in the LICENSE file. +

+

+Logica is not an officially supported Google product. +

+ +
+ + diff --git a/docs/logica_logo.png b/docs/logica_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..a973a3f4d688229112c58d826c19701c172086e0 GIT binary patch literal 44280 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 literal 0 HcmV?d00001 diff --git a/docs/logica_syntax.css b/docs/logica_syntax.css new file mode 100644 index 0000000..0bbbf41 --- /dev/null +++ b/docs/logica_syntax.css @@ -0,0 +1,57 @@ +/* +Copyright 2023 Google LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. */ + + +@import url('https://fonts.googleapis.com/css2?family=Roboto+Mono&display=swap'); +.plain_code, .code { + background-color: #252525; + color: #eee; + white-space: pre; + padding: 10px; + padding-top: 0px; + padding-bottom: 20px; + border-radius: 7px; + font-family: 'Roboto Mono', monospace; + margin-top: 15px; + margin-bottom: 20px; + width: auto; + overflow-x: auto; +} +.agg_field, .operator, .variable, .predicate, .field, .keyword, .string, .end_of_line_comment, .multiline_comment { + display: inline; +} +.predicate { + display: inline; + /* color: #7c7; */ + color: #eeb; +} +.field { + color: #6ee; +} +.agg_field { + color: #6ee; +} +.end_of_line_comment, .multiline_comment { + color: #aaa; +} +.string { + color: #c77; +} +.keyword { + color: #c8c; +} +.variable { + color: #ddd; +} \ No newline at end of file diff --git a/docs/sandbox.html b/docs/sandbox.html new file mode 100644 index 0000000..85e9f99 --- /dev/null +++ b/docs/sandbox.html @@ -0,0 +1,312 @@ + + + + + + + + + + + + + + + + + +
+ +
+ Program: +
+ +
+# Welcome to Logica Playground!
+# Go ahead and edit this program. Start with editing book names and prices.
+# You can write new predicates, specify which predicate you want to
+# run in the input box below.
+Book("From Caves to Stars", 120);
+Book("Morning of Juliet", 40);
+Book("Dawn of the Apes", 45);
+Book("Tragedy and Discord", 124);
+Book("How to Get Rich Writing Books for $5", 5);
+Book("I Wrote a Book for $5, Guess What Happened Next!", 4);
+Book("Breakfast at Paris", 30);
+Book("My Friend: Dragon", 102);
+
+ExpensiveBook(book_name) :-
+  Book(book_name, price),
+  price > 100;
+  
+
+
Predicate to run:
+ + + +
+
+ Output: +
+
+
+ + + + packages = ["logica", "sqlite3"] + + +from logica.parser_py import parse +from logica.compiler import universe +from logica.compiler import rule_translate +from logica.common import logica_lib +from logica.common import color + +color.CHR_WARNING = '{logica error}-*' +color.CHR_END = '*-{logica error}' + +def RunPredicate(program, predicate): + try: + rules = parse.ParseFile(program)['rule'] + except parse.ParsingException as e: + before, error, after = e.location.Pieces() + error_context = before + "{logica error}-*" + error + "*-{logica error}" + after; + return {"result": "", "error_context": error_context, "error_message": str(e), "status": "error", "predicate_name": predicate} + try: + u = universe.LogicaProgram(rules) + sql = u.FormattedPredicateSql(predicate) + except rule_translate.RuleCompileException as e: + return {"result": "", "error_context": e.rule_str, "error_message": str(e), "status": "error", "predicate_name": predicate} + + try: + data = logica_lib.RunQuery(sql, engine='sqlite') + except Exception as e: + return {"result": "", "error_context": sql, "error_message": "Error while executing SQL:\n%s" % e, "status": "error", "predicate_name": predicate} + + + return {"result": data, "error_message": "", "error_context": "", "status": "OK", "predicate_name": predicate} + +
+ + \ No newline at end of file diff --git a/docs/syntax_highlighting.js b/docs/syntax_highlighting.js new file mode 100644 index 0000000..20ab0b1 --- /dev/null +++ b/docs/syntax_highlighting.js @@ -0,0 +1,69 @@ +/* +Copyright 2023 Google LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. */ + + +function HighlightCodeElements(doc) { + console.log('HELLO!'); + synt_obj = { + multiline_comment: /\/\*.*\*\//gs, + end_of_line_comment: /#.*/g, + operator: />|</g, + string: /["].*["]/g, + predicate: /[A-Z]\w*/g, + field: /\w+:/g, + agg_field: /\w+[?]/g, + keyword: / if | then | else | distinct | in /g, + variable: /[a-z]\w*/g + }; + + function Decorate(s, r, prefix, suffix, subdecor) { + let result = '' + let from_index = 0; + s.replace(r, (match, at, unused_s) => { + result += subdecor(s.slice(from_index, at)); + result += prefix + match + suffix; + from_index = at + match.length; + return ''; + }); + + result += subdecor(s.slice(from_index, s.length)); + return result; + } + + var syntax_elements = Object.entries(synt_obj); + + function MakeDecorator(syntax_elements) { + let n, r, rest; + [[n, r], ...rest] = syntax_elements; + + let subdecorator; + if (rest.length > 0) { + subdecorator = MakeDecorator(rest); + } else { + subdecorator = x => x; + } + return s => Decorate(s, r, `
`, `
`, subdecorator); + } + + function HighlightLogicaCode(code) { + highlighter = MakeDecorator(syntax_elements); + return highlighter(code); + } + + divs = doc.getElementsByClassName('code'); + for (let d of divs) { + d.innerHTML = HighlightLogicaCode(d.innerText); + } +} \ No newline at end of file
+ + + + + +
+ + + + + + Playground(🏐) +
+