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

[Doc] Update faq and ggui, and add them to CI #7861

Merged
merged 15 commits into from
Apr 24, 2023
106 changes: 88 additions & 18 deletions docs/conftest.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
# -*- coding: utf-8 -*-

# -- stdlib --
from dataclasses import dataclass
from functools import wraps
from itertools import count
from re import M
from typing import List, Optional, Dict
import linecache
import sys
import uuid
import warnings
from dataclasses import dataclass
from functools import wraps
from itertools import count
from typing import List, Optional

# -- third party --
from pytest import ExceptionInfo
import marko
import matplotlib.pyplot as plt
import pytest
from pytest import ExceptionInfo

import taichi as ti

# -- own --
Expand Down Expand Up @@ -58,12 +59,6 @@
M = 8
"""

PRELUDES[
"gui"
] = """
gui = ti.GUI('Title', res=(400, 400))
"""


def hook(module, name=None):
def inner(hooker):
Expand All @@ -81,6 +76,9 @@ def real_hooker(*args, **kwargs):
return inner


GUI_WINDOW = None


@hook(ti.GUI)
def show(orig, self, *args, **kwargs):
if not self.running:
Expand All @@ -92,28 +90,85 @@ def show(orig, self, *args, **kwargs):
return orig(self, *args, *kwargs)


@hook(ti.GUI)
def __init__(orig, self, *args, **kwargs):
global GUI_WINDOW
assert not GUI_WINDOW
orig(self, *args, **kwargs)
GUI_WINDOW = self


@hook(ti.GUI)
def close(orig, self):
global GUI_WINDOW
assert not GUI_WINDOW or self is GUI_WINDOW
GUI_WINDOW = None
return orig(self)


GGUI_WINDOW = None


@hook(ti.ui.Window)
def show(orig, self, *args, **kwargs):
if not self.running:
self.close()
self.destroy()
return

self._frames_remaining -= 1

return orig(self, *args, *kwargs)


@hook(ti.ui.Window)
def __init__(orig, self, *args, **kwargs):
global GGUI_WINDOW
assert not GGUI_WINDOW
orig(self, *args, **kwargs)
GGUI_WINDOW = self


@hook(ti.ui.Window)
def destroy(orig, self):
global GGUI_WINDOW
assert not GGUI_WINDOW or self is GGUI_WINDOW
GGUI_WINDOW = None
return orig(self)


@hook(plt)
jim19930609 marked this conversation as resolved.
Show resolved Hide resolved
def show(orig):
return


@hook(plt)
def imshow(orig, img):
return


_prop_running = property(
(lambda self: self._frames_remaining > 0),
(lambda self, v: None),
)

ti.GUI._frames_remaining = 10
ti.GUI.running = property(lambda self: self._frames_remaining <= 0)
ti.GUI.running = _prop_running
ti.ui.Window._frames_remaining = 10
ti.ui.Window.running = property(lambda self: self._frames_remaining <= 0)
ti.ui.Window.running = _prop_running


def pytest_runtest_teardown(item, nextitem):
global GUI_WINDOW, GGUI_WINDOW
GUI_WINDOW and GUI_WINDOW.close()
GGUI_WINDOW and GGUI_WINDOW.destroy()


@dataclass
class PythonSnippet:
name: str
code: str
skip: Optional[str]
per_file_preludes: Dict[str, str]
known_error: bool = False
preludes: Optional[List[str]] = None

Expand All @@ -131,13 +186,21 @@ def collect(self):
if bad_tags:
raise ValueError(f"Invalid language tag {bad_tags} in markdown file")

per_file_preludes = {}

spec = None
for name, c in codes:
if not c.lang == "python":
continue
extra = dict((v.split(":", 1) + [None])[:2] for v in c.extra.split())
code = c.children[0].children
if "cont" in extra:
if "as-prelude" in extra:
assert "cont" not in extra
assert "preludes" not in extra
prelude_name = extra["as-prelude"]
assert prelude_name not in per_file_preludes, f"Duplicate prelude {prelude_name}"
per_file_preludes[prelude_name] = code
elif "cont" in extra:
assert spec is not None
spec.code += code
else:
Expand All @@ -153,6 +216,7 @@ def collect(self):
code=code,
skip=extra.get("skip-ci"),
known_error="known-error" in extra,
per_file_preludes=per_file_preludes,
preludes=preludes,
)

Expand Down Expand Up @@ -215,8 +279,14 @@ def runtest(self):
else:
preludes.insert(0, "init")

source = [PRELUDES[p] for p in preludes] + [spec.code]
source = "".join(source)
snippets = []
for p in preludes:
c = spec.per_file_preludes.get(p)
c = c or PRELUDES.get(p)
assert c is not None, f"Unknown prelude {p}"
snippets.append(c)
snippets.append(spec.code)
source = "".join(snippets)
fn = f"<snippet:{uuid.uuid4()}>"
code = compile(source, fn, "exec")
linecache.cache[fn] = (
Expand Down
2 changes: 2 additions & 0 deletions docs/cover-in-ci.lst
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ docs/lang/articles/kernels/kernel_sync.md
docs/lang/articles/performance_tuning/performance.md
docs/lang/articles/performance_tuning/profiler.md
docs/lang/articles/reference/global_settings.md
docs/lang/articles/faqs/faq.md
docs/lang/articles/visualization/ggui.md
docs/lang/articles/visualization/gui_system.md
docs/rfcs/20220410-rfc-process.md
docs/rfcs/yyyymmdd-rfc-template.md
69 changes: 43 additions & 26 deletions docs/lang/articles/faqs/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ You may have a Python interpreter with an unsupported version. Currently, Taichi

### Outer-most loops in Taichi kernels are by default parallel. How can I **serialize** one of them?

A solution is to add an additional *ghost* loop with only one iteration outside the loop you want to serialize.
A solution is to add an additional _ghost_ loop with only one iteration outside the loop you want to serialize.

```python {1}
for _ in range(1): # This "ghost" loop will be "parallelized", but with only one thread. Therefore, the containing loop below is serialized.
Expand All @@ -28,7 +28,6 @@ You can call `ti.sync()`, which is similar to CUDA's `cudaStreamSynchronize()`,

`__syncthreads()` is a block-level synchronization barrier, and Taichi provides a synonymous API `ti.simt.block.sync()`, which for now supports CUDA and Vulkan backends only. However, all block-level APIs are still experimental, and you should use this API only when it relates to SIMT operation synchronization and `SharedArray` reads and writes.


## Data structures

### How do I declare a field with a **dynamic length**?
Expand All @@ -49,6 +48,9 @@ Direct value assignments lead to semantic ambiguity. For example, `a = b` can me
You can swap two fields in the Taichi scope using a struct for:

```python
a = ti.field(ti.i32, 32)
b = ti.field(ti.i32, 32)

@ti.func
def field_copy(src: ti.template(), dst: ti.template()):
for I in ti.grouped(src):
Expand All @@ -65,13 +67,26 @@ test()

### How do I compute the minimum/maximum of a field?

Use `ti.automic.min/max` instead of `ti.min/max`. For example:
Use `ti.atomic_min/atomic_max` instead of `ti.min/max`. For example:

```python
for i in x:
ret = ti.atomic_min(ret, x[i])
x = ti.field(ti.f32, 32)

@ti.kernel
def x_min() -> ti.f32:
ret: ti.f32 = x[0]

for i in x:
ret = ti.atomic_min(ret, x[i])

return ret

x_min()
```

### Does Taichi support `bool` type?

Currently, Taichi does not support the `bool` type.

### How do I program on less structured data structures (such as graphs and tetrahedral meshes) in Taichi?

Expand All @@ -92,6 +107,7 @@ Yes, Taichi's Python user-facing APIs should work natively with any language ser
Take VSCode as an example, you can install `Python` or `Pylance` extensions to get language support like signature help with type information, code completion etc.

If it doesn't work out of box after installing the extension, please make sure the right Python interpreter is selected by:

- invoke command palette (`Shift + Command + P (Mac) / Ctrl + Shift + P (Windows/Linux)`)
- find `Python: Select Interpreter`
- make sure you select the path to the Python interpreter you're using with a `taichi` package installed
Expand All @@ -106,15 +122,14 @@ Follow these steps to install Taichi on a server without Internet access.
pip download taichi
```

*This command downloads the wheel package of Taichi and all its dependencies.*
_This command downloads the wheel package of Taichi and all its dependencies._

2. Copy the downloaded *.whl packages to your local server and install each with the following command. Note that you *must* complete all dependency installation before installing Taichi.
2. Copy the downloaded *.whl packages to your local server and install each with the following command. Note that you *must\* complete all dependency installation before installing Taichi.

```
python -m pip install xxxx.whl
```


## Integration with other libs/softwares

### What is the most convenient way to load images into Taichi fields?
Expand All @@ -127,17 +142,23 @@ Yes, Taichi supports many popular Python packages. Taichi provides helper functi

```python
import taichi as ti
pixels = ti.field(ti.f32, (1024, 512))
import numpy as np
arr = np.random.rand(1024, 512)
pixels.from_numpy(arr) # load numpy data into taichi fields
import matplotlib.pyplot as plt

pixels = ti.field(ti.f32, (512, 512))

def render_pixels():
arr = np.random.rand(512, 512)
pixels.from_numpy(arr) # load numpy data into taichi fields

render_pixels()
arr = pixels.to_numpy() # store taichi data into numpy arrays
plt.imshow(arr)
plt.show()
import matplotlib.cm as cm
cmap = cm.get_cmap('magma')
gui = ti.GUI('Color map')
gui = ti.GUI('Color map', (512, 512))

while gui.running:
render_pixels()
arr = pixels.to_numpy()
Expand All @@ -147,14 +168,12 @@ while gui.running:

Besides, you can also pass numpy arrays or torch tensors into a Taichi kernel as arguments. See [Interacting with external arrays](../basic/external.md) for more details.


### Can I integrate Taichi and Houdini?

The answer is an unequivocal Yes! Our contributors managed to embed [taichi_elements](https://github.com/taichi-dev/taichi_elements), a multi-material continuum physics engine, into Houdini as an extension, combining Houdini's flexibility in preprocessing with Taichi's strength in high-performance computation.

You can follow the instructions provided [here](https://github.com/taichi-dev/taichi_houdini).


## Precision related

### How do I accurately initialize a vector or matrix with `f64` precision when my default floating-point precision (`default_fp`) is `f32`?
Expand Down Expand Up @@ -196,7 +215,7 @@ A Taichi kernel **cannot** take a Python list directly. You need to use NumPy ar

For example, the following code snippet does not work:

```python
```python skip-ci:Error
import taichi as ti
import numpy as np
ti.init()
Expand Down Expand Up @@ -233,18 +252,16 @@ Taichi's GUI system can display colors when the field it accepts is a 3D vector

To enable color mapping, convert `ti.field` into a NumPy array and call Matplotlib's colormap (`cm`), as shown in the following example:

```python
gui = ti.GUI(f'Re = {un * lx / mu:4.0f} V_mag', (nx+1, ny+1))
```python skip-ci:Trivial
pixels = ti.Vector.field(3, shape=(w, h))
gui = ti.GUI(f'Window title', (w, h))
step = 0
while gui.running: # Main loop
print(f'>>> step : {step:<6d}, time : {step*dt:<6.3f} sec')
substep()
if step % 10 = 1:
V_np = V_mag.to_numpy()
V_img = cm.jet(V_np)
gui.set_image(V_img) # Plot the velocity magnitude contour
gui.show()
step += 1
simulate_one_substep(pixels)
img = pixels.to_numpy()
img = cm.jet(img)
gui.set_image(img)
gui.show()
```

## Objective-oriented programming
Expand Down Expand Up @@ -328,7 +345,7 @@ And data in fields `x` and `y` can be found in files **x.txt** and **y.txt**, re

Taichi fields adopt a different coordinate system from NumPy's arrays for storing images. In a Taichi field, [0,0] denotes the pixel at the lower left corner of the image; the first axis extends to the right of the image; the second axis extends to the top.

This is different from the usual convention taken by popular third-party libs like `matplotlib` or `opencv`, where [0, 0] denotes the pixel at the top left corner, the first axis extends down to the bottom of the image, and the second axis extends to the right.
This is different from the usual convention taken by popular third-party libs like `matplotlib` or `opencv`, where [0, 0] denotes the pixel at the top left corner, the first axis extends down to the bottom of the image, and the second axis extends to the right.

Therefore, to display a NumPy array using `matplotlb`'s `imshow()`, you must rotate it 90 degrees clockwise.

Expand Down
Loading