Skip to content

Commit

Permalink
[mod] merge col-of-ellipse
Browse files Browse the repository at this point in the history
  • Loading branch information
NaokiHori committed Feb 10, 2024
1 parent f7383bc commit bc64913
Show file tree
Hide file tree
Showing 18 changed files with 981 additions and 69 deletions.
43 changes: 24 additions & 19 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
python -m pip install --upgrade pip
pip install numpy matplotlib
- name: Checkout repository
uses: actions/checkout@v1
uses: actions/checkout@main
with:
repository: 'NaokiHori/EllipsesInFlows'
ref: 'main'
Expand Down Expand Up @@ -67,7 +67,7 @@ jobs:
echo "Date :" $(date) >> artifacts/ci.txt
echo "Hash :" $(git rev-parse HEAD) >> artifacts/ci.txt
- name: Upload artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@main
with:
name: LateralMigration${{ matrix.resolution }}
path: artifacts
Expand All @@ -88,7 +88,7 @@ jobs:
python -m pip install --upgrade pip
pip install numpy matplotlib
- name: Checkout repository
uses: actions/checkout@v1
uses: actions/checkout@main
with:
repository: 'NaokiHori/EllipsesInFlows'
ref: 'main'
Expand Down Expand Up @@ -123,7 +123,7 @@ jobs:
echo "Date :" $(date) >> artifacts/ci.txt
echo "Hash :" $(git rev-parse HEAD) >> artifacts/ci.txt
- name: Upload artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@main
with:
name: SegreSilberberg${{ matrix.resolution }}
path: artifacts
Expand All @@ -144,7 +144,7 @@ jobs:
python -m pip install --upgrade pip
pip install numpy matplotlib
- name: Checkout repository
uses: actions/checkout@v1
uses: actions/checkout@main
with:
repository: 'NaokiHori/EllipsesInFlows'
ref: 'main'
Expand Down Expand Up @@ -182,7 +182,7 @@ jobs:
echo "Date :" $(date) >> artifacts/ci.txt
echo "Hash :" $(git rev-parse HEAD) >> artifacts/ci.txt
- name: Upload artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@main
with:
name: RotatingEllipse${{ matrix.resolution }}
path: artifacts
Expand All @@ -205,60 +205,65 @@ jobs:
- name: Install dependencies
run: |
sudo apt-get update && \
sudo apt-get -y install gnuplot ghostscript imagemagick
sudo apt-get -y install make gnuplot ghostscript imagemagick
- name: Edit ghostscript config
run: |
sudo sed -i 's/rights="none"/rights="read|write"/g' /etc/ImageMagick-6/policy.xml
- name: Checkout repository
uses: actions/checkout@v1
uses: actions/checkout@main
with:
repository: 'NaokiHori/EllipsesInFlows'
ref: 'main'
- name: Download LateralMigration16
uses: actions/download-artifact@v3
uses: actions/download-artifact@main
with:
name: LateralMigration16
path: docs/source/examples/case1/data/16
- name: Download LateralMigration32
uses: actions/download-artifact@v3
uses: actions/download-artifact@main
with:
name: LateralMigration32
path: docs/source/examples/case1/data/32
- name: Download LateralMigration48
uses: actions/download-artifact@v3
uses: actions/download-artifact@main
with:
name: LateralMigration48
path: docs/source/examples/case1/data/48
- name: Download SegreSilberberg032
uses: actions/download-artifact@v3
uses: actions/download-artifact@main
with:
name: SegreSilberberg32
path: docs/source/examples/case2/data/32
- name: Download SegreSilberberg064
uses: actions/download-artifact@v3
uses: actions/download-artifact@main
with:
name: SegreSilberberg64
path: docs/source/examples/case2/data/64
- name: Download SegreSilberberg96
uses: actions/download-artifact@v3
uses: actions/download-artifact@main
with:
name: SegreSilberberg96
path: docs/source/examples/case2/data/96
- name: Download RotatingEllipse48
uses: actions/download-artifact@v3
uses: actions/download-artifact@main
with:
name: RotatingEllipse48
path: docs/source/examples/case3/data/48
- name: Download RotatingEllipse64
uses: actions/download-artifact@v3
uses: actions/download-artifact@main
with:
name: RotatingEllipse64
path: docs/source/examples/case3/data/64
- name: Download RotatingEllipse96
uses: actions/download-artifact@v3
uses: actions/download-artifact@main
with:
name: RotatingEllipse96
path: docs/source/examples/case3/data/96
- name: Run collision scripts
run: |
cd docs/source/collision/data
make all
./a.out
- name: Create eps and tex files
run: |
cd docs
Expand Down Expand Up @@ -298,9 +303,9 @@ jobs:
sphinxdoc/sphinx:5.0.1 \
/bin/bash sphinx.sh
- name: Setup GitHub Pages
uses: actions/configure-pages@v1
uses: actions/configure-pages@main
- name: Upload HTML
uses: actions/upload-pages-artifact@v1
uses: actions/upload-pages-artifact@main
with:
path: docs/build/html
- name: Deploy to GitHub Pages
Expand Down
Empty file removed docs/source/_static/.gitignore
Empty file.
35 changes: 35 additions & 0 deletions docs/source/collision/data/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
CC := cc
CFLAGS := -O3 -std=c99 -Wall -Wextra
DEPEND := -MMD
LIBS := -lm
INCLUDES := -Iinclude
SRCSDIR := src
OBJSDIR := obj
SRCS := $(foreach dir, $(shell find $(SRCSDIR) -type d), $(wildcard $(dir)/*.c))
OBJS := $(addprefix $(OBJSDIR)/, $(subst $(SRCSDIR)/,,$(SRCS:.c=.o)))
DEPS := $(addprefix $(OBJSDIR)/, $(subst $(SRCSDIR)/,,$(SRCS:.c=.d)))
TARGET := a.out

help:
@echo "all : create \"$(TARGET)\""
@echo "clean : remove \"$(TARGET)\" and object files \"$(OBJSDIR)/*.o\""
@echo "help : show this help message"

all: $(TARGET)

$(TARGET): $(OBJS)
$(CC) $(CFLAGS) $(DEPEND) -o $@ $^ $(LIBS)

$(OBJSDIR)/%.o: $(SRCSDIR)/%.c
@if [ ! -e `dirname $@` ]; then \
mkdir -p `dirname $@`; \
fi
$(CC) $(CFLAGS) $(DEPEND) $(INCLUDES) -c $< -o $@

clean:
$(RM) -r $(OBJSDIR) $(TARGET)

-include $(DEPS)

.PHONY : help all clean

190 changes: 190 additions & 0 deletions docs/source/collision/data/example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
import numpy as np
from matplotlib import pyplot
from matplotlib import patches


def shift_and_rotate(disp, angle, p0):
# shift a point p0, then rotate it
# return the resulting point p1
p1 = dict()
x = p0["x"]+disp["x"]
y = p0["y"]+disp["y"]
p1["x"] = np.cos(angle)*x - np.sin(angle)*y
p1["y"] = np.sin(angle)*x + np.cos(angle)*y
return p1


def rotate_and_shift(disp, angle, p0):
# rotate a point (x0, y0), then shift it
# return the resulting point p1
p1 = dict()
x = p0["x"]
y = p0["y"]
p1["x"] = disp["x"] + np.cos(angle)*x - np.sin(angle)*y
p1["y"] = disp["y"] + np.sin(angle)*x + np.cos(angle)*y
return p1


def compute_evolute(e, t):
# compute center of evolute and radius of fitted circle
evolute = dict()
a = e["a"]
b = e["b"]
evolute["x"] = a*(1.-np.power(b/a, 2.))*np.power(np.cos(t), 3.)
evolute["y"] = b*(1.-np.power(a/b, 2.))*np.power(np.sin(t), 3.)
num = np.power(np.power(a*np.sin(t), 2.)+np.power(b*np.cos(t), 2.), 1.5)
den = a*b
evolute["r"] = num/den
return evolute


def find_normal_t(e, p):
# compute parameter t, with which a point (a*cos(t), b*sin(t))
# and the target point p gives a normal vector to the given ellipse
# ref: https://blog.chatfield.io/simple-method-for-distance-to-ellipse/
t = 0.25*np.pi
while True:
xe = e["a"]*np.cos(t)
ye = e["b"]*np.sin(t)
evolute = compute_evolute(e, t)
dxe = xe-evolute["x"]
dye = ye-evolute["y"]
dxp = abs(p["x"])-evolute["x"]
dyp = abs(p["y"])-evolute["y"]
norme = np.hypot(dxe, dye)
normp = np.hypot(dxp, dyp)
dc = norme*np.arcsin((dxe*dyp-dye*dxp)/(norme*normp))
dt = dc/np.sqrt(e["a"]**2.+e["b"]**2.-xe**2.-ye**2.)
t += dt
# limit in the first quadrant
t = min(t, 0.5*np.pi)
t = max(t, 0.)
if abs(dt) < 1.e-8:
break
# apply result to the other quadrants
if p["x"] < 0.:
t = -t+np.pi
if p["y"] < 0.:
t = -t
return t


def fit_circles(e0, e1):
# core function, fitting two circles
# read the documentation
for n in range(10):
if n == 0:
# initialise temporary evolute
p0 = {"x": e0["x"], "y": e0["y"]}
p1 = {"x": e1["x"], "y": e1["y"]}
# forward coordinate transform
disp = {"x": -e0["x"], "y": -e0["y"]}
angle = -e0["theta"]
p1_ = shift_and_rotate(disp, angle, p1)
# forward coordinate transform
disp = {"x": -e1["x"], "y": -e1["y"]}
angle = -e1["theta"]
p0_ = shift_and_rotate(disp, angle, p0)
# find optimum parameter t for each ellipse
e0_t = find_normal_t(e0, p1_)
e1_t = find_normal_t(e1, p0_)
# compute evolutes and their radii
evolute0_ = compute_evolute(e0, e0_t)
evolute1_ = compute_evolute(e1, e1_t)
# backward coordinate transform
disp = {"x": +e0["x"], "y": +e0["y"]}
angle = +e0["theta"]
p0 = rotate_and_shift(disp, angle, evolute0_)
# backward coordinate transform
disp = {"x": +e1["x"], "y": +e1["y"]}
angle = +e1["theta"]
p1 = rotate_and_shift(disp, angle, evolute1_)
evolute0 = {"x": p0["x"], "y": p0["y"], "r": evolute0_["r"]}
evolute1 = {"x": p1["x"], "y": p1["y"], "r": evolute1_["r"]}
return evolute0, evolute1


def plot_result(e0, e1, c0, c1):
# wrapper of matplotlib.patches to visualise
# resulting ellipses and fitted circles
def get_patch_of_ellipse(e):
x = e["x"]
y = e["y"]
width = 2.*e["a"]
height = 2.*e["b"]
angle = 180./np.pi*e["theta"]
keywords = {
"xy": (x, y),
"width": width,
"height": height,
"angle": angle,
"fc": "none",
"ec": "#FF0000",
}
return patches.Ellipse(**keywords)

def get_patch_of_circle(c):
x = c["x"]
y = c["y"]
radius = c["r"]
keywords = {
"xy": (x, y),
"radius": radius,
"fc": "none",
"ec": "#0000FF",
}
return patches.Circle(**keywords)

def compute_bounds(es, cs):
# determine plot ranges
# i.e., arguments of set_[xy]lim
xmin = +np.inf
xmax = -np.inf
ymin = +np.inf
ymax = -np.inf
for e in es:
xmin = min(xmin, e["x"]-max(e["a"], e["b"]))
xmax = max(xmax, e["x"]+max(e["a"], e["b"]))
ymin = min(ymin, e["y"]-max(e["a"], e["b"]))
ymax = max(ymax, e["y"]+max(e["a"], e["b"]))
for c in cs:
xmin = min(xmin, c["x"]-c["r"])
xmax = max(xmax, c["x"]+c["r"])
ymin = min(ymin, c["y"]-c["r"])
ymax = max(ymax, c["y"]+c["r"])
margin = 0.25
xmin -= margin
xmax += margin
ymin -= margin
ymax += margin
return (xmin, xmax), (ymin, ymax)
# prepare matplotlib objects
fig = pyplot.figure()
ax = fig.add_subplot(111)
# draw ellipses and circles
ax.add_patch(get_patch_of_ellipse(e0))
ax.add_patch(get_patch_of_ellipse(e1))
ax.add_patch(get_patch_of_circle(c0))
ax.add_patch(get_patch_of_circle(c1))
# set auxiliary parameters
xlim, ylim = compute_bounds([e0, e1], [c0, c1])
keywords = {
"xlim": xlim,
"ylim": ylim,
"aspect": "equal",
}
ax.set(**keywords)
# visualise
pyplot.show()
# clean-up
pyplot.close()


if __name__ == "__main__":
# initialise ellipses
e0 = {"a": 2.0, "b": 1.5, "x": 0.5, "y": 0.5, "theta": 0.2}
e1 = {"a": 1.5, "b": 1.0, "x": 2.0, "y": 2.5, "theta": 2.0}
# fit circles
c0, c1 = fit_circles(e0, e1)
# plot
plot_result(e0, e1, c0, c1)
Loading

0 comments on commit bc64913

Please sign in to comment.