aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTrygve Laugstøl <trygvis@inamo.no>2019-05-27 08:52:38 +0200
committerTrygve Laugstøl <trygvis@inamo.no>2019-05-27 08:52:38 +0200
commit0259c6f907875b54e5d1df4bc89efa1c293d2812 (patch)
treed27f62ac6eb6fc09d8e989088978d959176ca6dd
parent101d17f0993795769fa125d26ceec71ccfecd057 (diff)
downloadee-python-0259c6f907875b54e5d1df4bc89efa1c293d2812.tar.gz
ee-python-0259c6f907875b54e5d1df4bc89efa1c293d2812.tar.bz2
ee-python-0259c6f907875b54e5d1df4bc89efa1c293d2812.tar.xz
ee-python-0259c6f907875b54e5d1df4bc89efa1c293d2812.zip
Adding Soufflé based reasoning on parts. Replacing default configuration
with applying python function with this new reasoner.
-rw-r--r--src/ee/souffle/digikey.dl3
-rw-r--r--src/ee/souffle/kicad.dl43
-rw-r--r--src/ee/tools/__init__.py6
-rw-r--r--src/ee/tools/ninja.py1
-rw-r--r--src/ee/tools/part_apply_souffle_post.py56
-rw-r--r--src/ee/tools/part_apply_souffle_pre.py64
-rw-r--r--src/ee/tools/souffle-export.py51
-rw-r--r--src/ee/tools/templates/build.ninja.j238
-rw-r--r--test/souffle/test.souffle29
9 files changed, 289 insertions, 2 deletions
diff --git a/src/ee/souffle/digikey.dl b/src/ee/souffle/digikey.dl
new file mode 100644
index 0000000..735dd2d
--- /dev/null
+++ b/src/ee/souffle/digikey.dl
@@ -0,0 +1,3 @@
+#include "facts.dl"
+
+.output fact
diff --git a/src/ee/souffle/kicad.dl b/src/ee/souffle/kicad.dl
new file mode 100644
index 0000000..5448b05
--- /dev/null
+++ b/src/ee/souffle/kicad.dl
@@ -0,0 +1,43 @@
+#include "facts.dl"
+
+.output fact
+
+/*
+.decl is_resistor(uri:symbol)
+.decl is_capacitor(uri:symbol)
+.decl is_inductor(uri:symbol)
+
+is_resistor(Ref) :-
+ resistor_lib(lib).
+ fact(Ref, "http://purl.org/ee/kicad-sch-fact-type#footprint-library", lib).
+
+is_capacitor(Ref) :-
+ capacitor_lib(lib).
+ fact(Ref, "http://purl.org/ee/kicad-sch-fact-type#footprint-library", lib).
+
+is_inductor(Ref) :-
+ inductor_lib(lib).
+ fact(Ref, "http://purl.org/ee/kicad-sch-fact-type#footprint-library", lib).
+*/
+
+.decl resistor_lib(name:symbol)
+resistor_lib("Resistor_SMD").
+resistor_lib("Resistor_THT").
+
+.decl capacitor_lib(name:symbol)
+capacitor_lib("Capacitor_SMD").
+
+.decl inductor_lib(name:symbol)
+inductor_lib("Inductor_SMD").
+
+fact(Ref, "http://purl.org/ee/fact-type/ee-component-type", "http://purl.org/ee/part-type#resistor") :-
+ resistor_lib(lib),
+ fact(Ref, "http://purl.org/ee/kicad-sch-fact-type#footprint-library", lib).
+
+fact(Ref, "http://purl.org/ee/fact-type/ee-component-type", "http://purl.org/ee/part-type#capacitor") :-
+ capacitor_lib(lib),
+ fact(Ref, "http://purl.org/ee/kicad-sch-fact-type#footprint-library", lib).
+
+fact(Ref, "http://purl.org/ee/fact-type/ee-component-type", "http://purl.org/ee/part-type#inductor") :-
+ inductor_lib(lib),
+ fact(Ref, "http://purl.org/ee/kicad-sch-fact-type#footprint-library", lib).
diff --git a/src/ee/tools/__init__.py b/src/ee/tools/__init__.py
index 46004e9..112e306 100644
--- a/src/ee/tools/__init__.py
+++ b/src/ee/tools/__init__.py
@@ -26,5 +26,11 @@ def mk_parents(path: Union[str, Path]):
if len(dirname) == 0:
return
+ mk_dirs(dirname)
+
+
+def mk_dirs(path: Union[str, Path]):
+ dirname = str(path)
+
if not os.path.isdir(dirname):
os.mkdir(dirname)
diff --git a/src/ee/tools/ninja.py b/src/ee/tools/ninja.py
index 9e919fb..c5ee959 100644
--- a/src/ee/tools/ninja.py
+++ b/src/ee/tools/ninja.py
@@ -58,6 +58,7 @@ def generate(project: Project):
"ee": "{} -m ee".format(os.path.relpath(sys.executable, Path("."))),
"project": project,
"part_dbs": part_dbs,
+ "souffle_ee_src": Path(__file__).parent.parent / "souffle",
}
gerber_zip = None
diff --git a/src/ee/tools/part_apply_souffle_post.py b/src/ee/tools/part_apply_souffle_post.py
new file mode 100644
index 0000000..4c23128
--- /dev/null
+++ b/src/ee/tools/part_apply_souffle_post.py
@@ -0,0 +1,56 @@
+import argparse
+import csv
+from pathlib import Path
+
+from ee import EeException
+from ee.db import ObjDb
+from ee.part import Part, load_db, save_db, PartDb
+
+
+def work(in_path: Path, out_path: Path, work_dir: Path):
+ in_parts: ObjDb[Part] = ObjDb[Part]()
+ uri_idx = in_parts.add_unique_index("uri", lambda p: p.uri)
+
+ for entry in load_db(in_path).parts:
+ in_parts.add(Part(entry.part))
+
+ out_parts = PartDb()
+
+ for part in in_parts:
+ out_parts.add_entry(part, False)
+
+ with (work_dir / "out" / "fact.csv").open("r") as f:
+ reader = csv.reader(f, dialect="excel-tab")
+ for uri, key, value in reader:
+ try:
+ part = uri_idx.get_single(uri)
+ except KeyError:
+ raise EeException("Unknown part found in output: uri={}".format(uri))
+
+ old_value = part.facts.get_value(key)
+
+ if old_value is None:
+ print("New fact for {}: {}={}".format(uri, key, value))
+ part.facts.add(key, value)
+
+ save_db(out_path, out_parts)
+
+
+parser = argparse.ArgumentParser()
+
+parser.add_argument("--in",
+ dest="in_path",
+ required=True,
+ metavar="PART DB")
+
+parser.add_argument("--out",
+ required=True,
+ metavar="PART DB")
+
+parser.add_argument("--work",
+ required=True,
+ metavar="DIR")
+
+args = parser.parse_args()
+
+work(Path(args.in_path), Path(args.out), Path(args.work))
diff --git a/src/ee/tools/part_apply_souffle_pre.py b/src/ee/tools/part_apply_souffle_pre.py
new file mode 100644
index 0000000..d035ef7
--- /dev/null
+++ b/src/ee/tools/part_apply_souffle_pre.py
@@ -0,0 +1,64 @@
+import argparse
+import csv
+from pathlib import Path
+
+from ee import tools
+from ee.part import Part, load_db
+from ee.tools import mk_dirs
+
+
+def work(in_path: Path, work_dir: Path):
+ in_parts = load_db(in_path)
+
+ print_header = False
+
+ tools.mk_dirs(work_dir)
+ with (work_dir / "facts.dl").open("w") as f:
+ print(".decl part(uri:symbol, ref:symbol)", file=f)
+ print(".input part", file=f)
+ print("", file=f)
+ print(".decl fact(part_uri:symbol, key:symbol, value:symbol)", file=f)
+ print(".input fact", file=f)
+
+ part_count = 0
+ fact_count = 0
+
+ in_dir = work_dir / "in"
+ mk_dirs(in_dir)
+ with (in_dir / "part.facts").open("w") as part_f:
+ with (in_dir / "fact.facts").open("w") as fact_f:
+ part_csv = csv.writer(part_f, dialect="excel-tab")
+ if print_header:
+ part_csv.writerow(["uri", "ref"])
+
+ fact_csv = csv.writer(fact_f, dialect="excel-tab")
+ if print_header:
+ fact_csv.writerow(["part_uri", "key", "value"])
+
+ for xml in load_db(in_path).iterparts():
+ p = Part(xml)
+ sch_ref = p.get_only_schematic_reference()
+ part_csv.writerow([p.uri, sch_ref.referenceProp if sch_ref else None])
+ part_count += 1
+
+ for fact in p.get_facts():
+ fact_csv.writerow([p.uri, fact.keyProp, fact.valueProp])
+ fact_count += 1
+
+ print("Loaded clauses: {} parts, {} facts".format(part_count, fact_count))
+
+
+parser = argparse.ArgumentParser()
+
+parser.add_argument("--in",
+ dest="in_path",
+ required=True,
+ metavar="PART DB")
+
+parser.add_argument("--work",
+ required=True,
+ metavar="DIR")
+
+args = parser.parse_args()
+
+work(Path(args.in_path), Path(args.work))
diff --git a/src/ee/tools/souffle-export.py b/src/ee/tools/souffle-export.py
new file mode 100644
index 0000000..098eb11
--- /dev/null
+++ b/src/ee/tools/souffle-export.py
@@ -0,0 +1,51 @@
+import argparse
+import csv
+import os.path
+from pathlib import Path
+
+import ee.tools
+from ee.part import load_db, Part
+
+
+def run(args):
+ print_header = False
+
+ part_db = Path(args.part_db)
+ out_dir = part_db.parent / "{}.{}".format(os.path.splitext(part_db.name)[0], "souffle")
+
+ if not out_dir.is_dir():
+ ee.tools.mk_dirs(out_dir)
+
+ with open(out_dir / "facts.dl", "w") as f:
+ print(".decl part(uri:symbol, ref:symbol, supplier:symbol)", file=f)
+ print(".input part", file=f)
+ print("", file=f)
+ print(".decl fact(part_uri:symbol, key:symbol, value:symbol)", file=f)
+ print(".input fact", file=f)
+
+ with open(out_dir / "part.facts", "w") as part_f:
+ with open(out_dir / "fact.facts", "w") as fact_f:
+ part_csv = csv.writer(part_f, dialect="excel-tab")
+ if print_header:
+ part_csv.writerow(["uri", "ref", "supplier"])
+
+ fact_csv = csv.writer(fact_f, dialect="excel-tab")
+ if print_header:
+ fact_csv.writerow(["part_uri", "key", "value"])
+
+ for xml in load_db(part_db).iterparts():
+ p = Part(xml)
+ sch_ref = p.get_only_schematic_reference()
+ part_csv.writerow([p.uri, sch_ref.referenceProp if sch_ref else None, p.supplier])
+
+ for fact in p.get_facts():
+ fact_csv.writerow([p.uri, fact.keyProp, fact.valueProp])
+
+
+parser = argparse.ArgumentParser()
+
+parser.add_argument("--part-db",
+ required=True,
+ metavar="PART DB")
+
+run(parser.parse_args())
diff --git a/src/ee/tools/templates/build.ninja.j2 b/src/ee/tools/templates/build.ninja.j2
index 6ff4e0a..894365b 100644
--- a/src/ee/tools/templates/build.ninja.j2
+++ b/src/ee/tools/templates/build.ninja.j2
@@ -30,6 +30,15 @@ rule pn-part-search-list
rule part-apply-function
command = $ee part-apply-function --execution $execution --in $in --out $out $functions $arguments
+rule part-apply-souffle-pre
+ command = $ee part-apply-souffle-pre --in $in --work $work
+
+rule souffle
+ command = souffle -F $work/in -D $work/out -I $work -I "{{ souffle_ee_src }}" $in
+
+rule part-apply-souffle-post
+ command = $ee part-apply-souffle-post --in $in_sch --out $out --work $work
+
rule part-find-requirements
description = part-find-requirements
command = $ee part-find-requirements --in $in --out $out $report
@@ -74,8 +83,9 @@ build {{ gerber_zip }}: kicad-gerber $pcb
{% if sch is defined -%}
{%- set cfg = project.cfg["kicad-project"] %}
-build ee/kicad-sch.xml: kicad-make-bom $sch
-build ee/sch.xml: part-apply-function ee/kicad-sch.xml
+build ee/kicad/sch.xml: kicad-make-bom $sch
+{#
+build ee/sch.xml: part-apply-function ee/kicad/sch.xml
execution = kicad
{%- if cfg and "functions" in cfg and cfg["functions"] %}
functions = --function {{ cfg["functions"] }}
@@ -83,6 +93,17 @@ build ee/sch.xml: part-apply-function ee/kicad-sch.xml
{%- if cfg and "function-arguments" in cfg and cfg["function-arguments"] %}
arguments = --argument {{ cfg["function-arguments"] }}
{%- endif %}
+#}
+
+build ee/kicad/souffle/facts.dl: part-apply-souffle-pre ee/kicad/sch.xml
+ work = ee/kicad/souffle
+
+build ee/kicad/souffle/out/fact.csv: souffle {{ souffle_ee_src }}/kicad.dl | ee/kicad/souffle/facts.dl
+ work = ee/kicad/souffle
+
+build ee/sch.xml: part-apply-souffle-post ee/kicad/souffle/out/fact.csv
+ in_sch = ee/kicad/sch.xml
+ work = ee/kicad/souffle
{%- endif %}
build $report_dir/part-validate-parts.rst: part-validate-parts ee/sch.xml
@@ -101,6 +122,7 @@ build ee/{{ s }}/pn-part-search-list.xml: pn-part-search-list ee/sch.xml
build ee/{{ s }}/downloaded.xml | ee/{{ s }}/downloaded.rst: {{ s }}-search-parts ee/{{ s }}/pn-part-search-list.xml
{%- set reports=reports+["$report_dir/" + s + "/downloaded.rst"] %}
+{#
build ee/{{ s }}/parts.xml: part-apply-function ee/{{ s }}/downloaded.xml
execution = {{ s }}
{%- if cfg and "functions" in cfg and cfg["functions"] %}
@@ -109,6 +131,16 @@ build ee/{{ s }}/parts.xml: part-apply-function ee/{{ s }}/downloaded.xml
{%- if cfg and "function-arguments" in cfg and cfg["function-arguments"] %}
arguments = --argument {{ cfg["function-arguments"] }}
{%- endif %}
+#}
+build ee/{{ s }}/souffle/facts.dl: part-apply-souffle-pre ee/{{ s }}/downloaded.xml
+ work = ee/{{ s }}/souffle
+
+build ee/{{ s }}/souffle/out/fact.csv: souffle {{ souffle_ee_src }}/{{ s }}.dl | ee/{{ s }}/souffle/facts.dl
+ work = ee/{{ s }}/souffle
+
+build ee/{{ s }}/parts.xml: part-apply-souffle-post ee/{{ s }}/souffle/out/fact.csv
+ in_sch = ee/{{ s }}/downloaded.xml
+ work = ee/{{ s }}/souffle
{%- endfor %}
{%- for f in parts_yaml_files %}
@@ -149,3 +181,5 @@ build ee/seeed/opl/{{ opl }}.xml: seeed-download-opl
# Reports
build ee-reports: phony {{ " ".join(reports) }}
+build part-dbs: phony {%- for p in part_dbs %} {{ p }}.xml{% endfor %}
+build ee-all: phony ee-reports part-dbs
diff --git a/test/souffle/test.souffle b/test/souffle/test.souffle
new file mode 100644
index 0000000..1072e9a
--- /dev/null
+++ b/test/souffle/test.souffle
@@ -0,0 +1,29 @@
+.symbol_type Ref
+.symbol_type Uri
+.symbol_type Value
+
+.decl part(uri:Uri, supplier:Uri)
+.input part
+
+.decl fact(part:Uri, key:Uri, value:Value)
+.input fact
+
+.decl warning(part:Uri, msg:symbol)
+.output warning
+
+.decl part_status(part:Uri, status:Value)
+.decl active(part:Uri)
+
+part_status(part, status) :- fact(part, "http://purl.org/ee/digikey-fact-key#1989", status).
+
+active(part) :- part_status(part, "Active").
+
+warning(part, cat("Not active from ", supplier)) :- part(part, supplier), !active(part).
+
+/*
+active(part) :- fact(part, "http://purl.org/ee/digikey-fact-key#1989", "Active").
+
+warning(part, "Not active") :-
+ fact(part, "http://purl.org/ee/digikey-fact-key#1989", status),
+ status != "Active".
+*/ \ No newline at end of file