Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More tidying of implementations #303

Merged
merged 3 commits into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 15 additions & 19 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,25 +108,21 @@ python3 build.py _test_html --test lagrange,vector-lagrange --processes 4
```

### Adding an implementation
To add a library to the implementations section of DefElement, you must first add details of the
library to the file [`/data/implementations`](https://github.com/DefElement/defelement.com/blob/main/data/implementations).
You must include three key pieces of information about the library: its `name`, `url`, and a bash command to `install` it.
These three pieces of information are filed under an `id` for your library.

Once this has been done, you should next add the library to [`defelement/implementations.py`](https://github.com/DefElement/defelement.com/blob/main/defelement/implementations.py).
At the end of this file, there are three dictionaries, mapping the `id` of a library to a function.
You should add functions to these that do the following:

* The functions in `formats` take an implementation string and a set of parameters as inputs
and return the implementation information for the library, as it will be displayed on each
element's page.
* The functions in `examples` take a DefElement `Element` object as an input and return a block
of Python (as a string) that creates all the examples of that element using the library.
* [optional] The functions in `verifications` take a DefElement `Element` object, an example, and
a set of points as inputs and returns the element for that example tabulated at the set of points and
the number of DOFs associated with each sub-entity as a tuple of tuples.
The shape of the first output is `(number of points, value size, number of basis functions)`.
These functions are used to [verify](https://defelement.com/verification.html) that the implementation spans the same space as Symfem.
To add a library to the implementations section of DefElement, you must add a file containing to the folder
[`/defelement/implementations`](https://github.com/DefElement/defelement.com/blob/main/defelement/implementations).
This file should define a class that is a subclass of [`Implementation`](https://github.com/DefElement/defelement.com/blob/main/defelement/implementations/template.py).
This class should include:

| Item | Type | Use |
| -------------- | ------------------- | --- |
| `format` | method | This method should take an implementation string and a set of parameters as inputs and return the implementation information for the library, as it will be displayed on each element's page. |
| `example` | method | This method should take a DefElement `Element` object as an input and return a block of Python (as a string) that creates all the examples of that element using the library. |
| `verify` | method (optional) | This method should take a DefElement `Element` object, an example, and a set of points as inputs and returns the element for that example tabulated at the set of points and the number of DOFs associated with each sub-entity as a tuple of tuples. The shape of the first output is `(number of points, value size, number of basis functions)`. These functions are used to [verify](https://defelement.com/verification.html) that the implementation spans the same space as Symfem. |
| `id` | variable | The unique identifier for your library. This will be used in .def files. |
| `name` | variable | The name of your library. |
| `install` | variable | Code snippet to install you library (preferably using `pip3`) |
| `url` | variable | URL where the source code of your library is avaliable (eg a GitHub link). |
| `verification` | variable (optional) | Should be set to `True` if the `verification` function is implemented. |

Once these steps are done, you can start adding implementation details for your library to
the `implementation` field of elements in the [`elements`](https://github.com/DefElement/defelement.com/blob/main/elements)
Expand Down
29 changes: 14 additions & 15 deletions build.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from defelement.examples import markup_example
from defelement.families import keys_and_names
from defelement.html import make_html_page
from defelement.implementations import parse_example, verifications
from defelement.implementations import implementations, parse_example, verifications
from defelement.markup import (cap_first, heading_with_self_ref, insert_links, markup,
python_highlight)
from defelement.rss import make_rss
Expand Down Expand Up @@ -123,7 +123,6 @@ def write_html_page(path: str, title: str, content: str):
categoriser.load_categories(os.path.join(settings.data_path, "categories"))
categoriser.load_references(os.path.join(settings.data_path, "references"))
categoriser.load_families(os.path.join(settings.data_path, "families"))
categoriser.load_implementations(os.path.join(settings.data_path, "implementations"))

# Load elements from .def files
categoriser.load_folder(settings.element_path)
Expand Down Expand Up @@ -163,7 +162,7 @@ def write_html_page(path: str, title: str, content: str):
print(e.name)
content = heading_with_self_ref("h1", cap_first(e.html_name))
element_data = []
implementations = []
impl = []

# Link to ciarlet.html
content += "<p><small><a href='/ciarlet.html'>"
Expand Down Expand Up @@ -245,8 +244,8 @@ def write_html_page(path: str, title: str, content: str):

# Implementations
libraries = [
(i, j["name"], j["url"], j["install"])
for i, j in categoriser.implementations.items()
(i, j.name, j.url, j.install)
for i, j in implementations.items()
]
libraries.sort(key=lambda i: i[0])
for codename, libname, url, pip in libraries:
Expand Down Expand Up @@ -380,7 +379,7 @@ def write_html_page(path: str, title: str, content: str):
"}\n"
"</script>")

implementations.append(
impl.append(
(f"<a href='/lists/implementations/{libname}.html'>{libname}</a>", info))

# Categories
Expand All @@ -396,10 +395,10 @@ def write_html_page(path: str, title: str, content: str):
content += "</table>"

# Write implementations
if len(implementations) > 0:
if len(impl) > 0:
content += heading_with_self_ref("h2", "Implementations")
content += "<table class='element-info'>"
for i, j in implementations:
for i, j in impl:
content += f"<tr><td>{i.replace(' ', '&nbsp;')}</td><td>{j}</td></tr>"
content += "</table>"

Expand Down Expand Up @@ -560,8 +559,8 @@ def write_html_page(path: str, title: str, content: str):
for i in verifications:
if i != "symfem":
vs.append(i)
content += f"<td>{categoriser.implementations[i]['name']}</td>"
long_content += f"<td>{categoriser.implementations[i]['name']}</td>"
content += f"<td>{implementations[i].name}</td>"
long_content += f"<td>{implementations[i].name}</td>"
content += "</tr></thead>"
long_content += "</tr></thead>"
rows = []
Expand Down Expand Up @@ -669,7 +668,7 @@ def write_html_page(path: str, title: str, content: str):
for i in verifications:
c += (
"<tr>"
f"<td>{categoriser.implementations[i]['name']}</td>"
f"<td>{implementations[i].name}</td>"
f"<td><img src='/badges/{i}.svg'></td>"
"<td style='font-size:80%;font-family:monospace'>"
f"[![DefElement verification](https://defelement.com/badges/{i}.svg)]"
Expand Down Expand Up @@ -911,7 +910,7 @@ def build_examples(egs: typing.List[typing.Dict[str, str]], process: str = ""):
# Implementations index
os.mkdir(os.path.join(settings.htmlindices_path, "implementations"))
content = heading_with_self_ref("h1", "Implemented elements")
for c, info in categoriser.implementations.items():
for c, info in implementations.items():
category_pages = []
for e in categoriser.elements_in_implementation(c):
names = {e.html_name}
Expand All @@ -930,17 +929,17 @@ def build_examples(egs: typing.List[typing.Dict[str, str]], process: str = ""):

category_pages.sort(key=lambda x: x[0])

content += (f"<h2><a href='/lists/implementations/{c}.html'>Implemented in {info['name']}"
content += (f"<h2><a href='/lists/implementations/{c}.html'>Implemented in {info.name}"
"</a></h2>\n<ul>")
content += "".join([i[1] for i in category_pages])
content += "</ul>"

sub_content = f"<h1>Implemented in <a href='{info['url']}'>{info['name']}</a></h1>\n<ul>"
sub_content = f"<h1>Implemented in <a href='{info.url}'>{info.name}</a></h1>\n<ul>"
sub_content += "".join([i[1] for i in category_pages])
sub_content += "</ul>"

write_html_page(os.path.join(settings.htmlindices_path, f"implementations/{c}.html"),
f"Implemented in {info['name']}", sub_content)
f"Implemented in {info.name}", sub_content)

write_html_page(os.path.join(settings.htmlindices_path, "implementations/index.html"),
"Implemented elements", content)
Expand Down
28 changes: 0 additions & 28 deletions data/implementations

This file was deleted.

24 changes: 7 additions & 17 deletions defelement/element.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
import yaml
from github import Github

from defelement import implementations, settings
from defelement import settings
from defelement.families import arnold_logg_reference, cockburn_fu_reference, keys_and_names
from defelement.implementations import VariantNotImplemented
from defelement.implementations import VariantNotImplemented, examples, implementations
from defelement.markup import insert_links
from defelement.polyset import make_extra_info, make_poly_set

Expand Down Expand Up @@ -622,7 +622,7 @@ def list_of_implementation_strings(
assert self.implemented(lib)

if "display" in self.data["implementations"][lib]:
d = implementations.formats[lib](self.data["implementations"][lib]["display"], {})
d = implementations[lib].format(self.data["implementations"][lib]["display"], {})
return f"<code>{d}</code>"
if "variants" in self.data:
variants = self.data["variants"]
Expand All @@ -638,7 +638,7 @@ def list_of_implementation_strings(
continue
data = self.data["implementations"][lib][v]
if isinstance(data, str):
s = implementations.formats[lib](*self.get_implementation_string(lib, None, v))
s = implementations[lib].format(*self.get_implementation_string(lib, None, v))
if s not in i_dict:
i_dict[s] = []
if v is None:
Expand All @@ -647,7 +647,7 @@ def list_of_implementation_strings(
i_dict[s].append(vinfo["variant-name"])
else:
for i, j in data.items():
s = implementations.formats[lib](*self.get_implementation_string(lib, i, v))
s = implementations[lib].format(*self.get_implementation_string(lib, i, v))
if s not in i_dict:
i_dict[s] = []
if v is None:
Expand All @@ -672,7 +672,7 @@ def make_implementation_examples(self, lib: str) -> str:
Returns:
Examples
"""
return implementations.examples[lib](self)
return implementations[lib].example(self)

def has_implementation_examples(self, lib: str) -> bool:
"""Check if element has implementation examples for a library.
Expand All @@ -683,7 +683,7 @@ def has_implementation_examples(self, lib: str) -> bool:
Returns:
True if library has examples, otherwise False
"""
return lib in implementations.examples
return lib in examples

def categories(self, link: bool = True, map_name: bool = True) -> typing.List[str]:
"""Get categories.
Expand Down Expand Up @@ -780,7 +780,6 @@ def __init__(self):
self.families = {}
self.references = {}
self.categories = {}
self.implementations = {}

def recently_added(self, n: int) -> typing.List[Element]:
"""Get recently added elements.
Expand Down Expand Up @@ -820,15 +819,6 @@ def load_categories(self, file: str):
a, b = line.split(":", 1)
self.add_category(a.strip(), b.strip(), f"{a.strip()}.html")

def load_implementations(self, file: str):
"""Load implementations from a file.

Args:
file: Filename
"""
with open(file) as f:
self.implementations = yaml.load(f, Loader=yaml.FullLoader)

def load_families(self, file: str):
"""Load families from a file.

Expand Down
11 changes: 6 additions & 5 deletions defelement/implementations/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from defelement.implementations.template import Implementation, VariantNotImplemented, parse_example

implementations = []
implementations = {}
this_dir = os.path.dirname(os.path.realpath(__file__))
for file in os.listdir(this_dir):
if file.endswith(".py") and not file.startswith("_") and file != "template.py":
Expand All @@ -15,8 +15,9 @@
if not name.startswith("_"):
c = getattr(mod, name)
if isclass(c) and c != Implementation and issubclass(c, Implementation):
implementations.append(c)
assert c.id is not None
implementations[c.id] = c

formats = {i.name: i.format for i in implementations if i.name is not None}
examples = {i.name: i.example for i in implementations if i.name is not None}
verifications = {i.name: i.verify for i in implementations if i.verification and i.name is not None}
formats = {id: i.format for id, i in implementations.items()}
examples = {id: i.example for id, i in implementations.items()}
verifications = {id: i.verify for id, i in implementations.items() if i.verification}
7 changes: 5 additions & 2 deletions defelement/implementations/basix.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def format(string: typing.Optional[str], params: typing.Dict[str, typing.Any]) -

@staticmethod
def example(element: Element) -> str:
"""Generate Symfem examples.
"""Generate examples.

Args:
element: The element
Expand Down Expand Up @@ -104,5 +104,8 @@ def verify(
**kwargs)
return e.entity_dofs, lambda points: e.tabulate(0, points)[0].transpose((0, 2, 1))

name = "basix"
id = "basix"
name = "Basix"
url = "https://github.com/FEniCS/basix"
verification = True
install = "pip3 install fenics-basix"
7 changes: 5 additions & 2 deletions defelement/implementations/basix_ufl.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def format(string: typing.Optional[str], params: typing.Dict[str, typing.Any]) -

@staticmethod
def example(element: Element) -> str:
"""Generate Symfem examples.
"""Generate examples.

Args:
element: The element
Expand Down Expand Up @@ -119,5 +119,8 @@ def verify(
return e.entity_dofs, lambda points: e.tabulate(0, points)[0].reshape(
points.shape[0], e.reference_value_size, -1)

name = "basix.ufl"
id = "basix.ufl"
name = "Basix.UFL"
url = "https://github.com/FEniCS/basix"
verification = True
install = "pip3 install fenics-basix fenics-ufl"
7 changes: 5 additions & 2 deletions defelement/implementations/bempp.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def format(string: typing.Optional[str], params: typing.Dict[str, typing.Any]) -

@staticmethod
def example(element: Element) -> str:
"""Generate Symfem examples.
"""Generate examples.

Args:
element: The element
Expand Down Expand Up @@ -56,4 +56,7 @@ def example(element: Element) -> str:
out += f"\"{bempp_name}\", {ord})"
return out

name = "bempp"
id = "bempp"
name = "Bempp"
url = "https://github.com/bempp/bempp-cl"
install = "pip3 install numba scipy meshio\npip3 install bempp-cl"
7 changes: 5 additions & 2 deletions defelement/implementations/fiat.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def format(string: typing.Optional[str], params: typing.Dict[str, typing.Any]) -

@staticmethod
def example(element: Element) -> str:
"""Generate Symfem examples.
"""Generate examples.

Args:
element: The element
Expand Down Expand Up @@ -145,5 +145,8 @@ def verify(
return edofs, lambda points: list(e.tabulate(0, points).values())[0].T.reshape(
points.shape[0], value_size, -1)

name = "fiat"
id = "fiat"
name = "FIAT"
url = "https://github.com/firedrakeproject/fiat"
verification = True
install = "pip3 install git+https://github.com/firedrakeproject/fiat.git"
7 changes: 5 additions & 2 deletions defelement/implementations/symfem.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def format(string: typing.Optional[str], params: typing.Dict[str, typing.Any]) -

@staticmethod
def example(element: Element) -> str:
"""Generate Symfem examples.
"""Generate examples.

Args:
element: The element
Expand Down Expand Up @@ -146,5 +146,8 @@ def verify(
t = CachedSymfemTabulator(e)
return edofs, lambda points: t.tabulate(points)

name = "symfem"
id = "symfem"
name = "Symfem"
url = "https://github.com/mscroggs/symfem"
install = "pip3 install symfem"
verification = True
Loading
Loading