Skip to main content

Visual block-diagram simulation for control systems and robotics

Project description

Loopsym — visual block-diagram simulation for control systems and robotics, by Sentiery

교육용 파이썬 블록 다이어그램 시뮬레이터. Simulink 의 핵심 원리(블록·와이어· 실행 순서·solver)를 파이썬으로 직접 만들어 보는 수업용 엔진이자, 시뮬레이션 에서 실기체(ROS2/HILS)까지 같은 다이어그램으로 가는 드론/로봇 개발 플랫폼. 전체가 순수 파이썬이라 학생이 한 학기에 바닥부터 따라 만들 수 있는 규모다.

License: Apache-2.0 · Copyright 2026 Sentiery · Repository · Issues

뷰어는 데스크톱 전용입니다 (최소 폭 1024px 권장). 모바일/태블릿 레이아웃은 아직 지원하지 않습니다.

빠른 시작 (PyPI — 3단계)

python -m pip install loopsym
loopsym            # PI 폐루프 데모가 로드된 뷰어가 앱 창으로 열린다
                   # → ▶ 실행 → 아래 Scope 에 ref/y 두 채널 표시
  • 첫 실행은 인터넷이 필요합니다 — 브라우저가 Pyodide(파이썬 런타임)를 CDN 에서 내려받습니다 (수 초, 이후 캐시). 오프라인이면 상태줄 안내대로 python -m loopsym.server 를 켜고 새로고침하면 됩니다 ([server] extra 필요).
  • 다른 시작점: loopsym --blank (빈 캔버스), loopsym --example pendulum (번들 예제: pi·pendulum·discrete_pi), loopsym my_model.json (저장한 모델). loopsym --help 가 전체 형태를 설명합니다.

파이썬 코드로 직접 만들 수도 있습니다 (설치만으로 동작, 의존성 0):

from loopsym import *
import webbrowser

bd = Diagram()
ref   = bd.add(Step(t_on=1.0))
err   = bd.add(Sum("+-"))
plant = bd.add(TransferFunction([1], [1, 2, 1]))
ctrl  = bd.add(PyFunction("3.0*u0", n_in=1))     # <- MATLAB Function 블록처럼
scope = bd.add(Scope(n_in=2))

bd.connect(ref, err[0]); bd.connect(plant, err[1])
bd.connect(err, ctrl);   bd.connect(ctrl, plant)
bd.connect(ref, scope[0]); bd.connect(plant, scope[1])

run(bd, t_end=10.0, dt=0.01)
path = render_html(bd, "diagram.html")   # 브라우저에서 구조 확인/편집/재실행
print("뷰어:", path)
webbrowser.open("file://" + path)

소스에서 개발

git clone https://github.com/sentiery-labs/loopsym && cd loopsym
python -m pip install -e ".[examples]"     # matplotlib (ex1~ex4 의 PNG 플롯용)
python examples/ex1_pi_closed_loop.py      # ex1_result.png + ex1_diagram.html 생성

예제 카탈로그(난이도/의존성/생성물)는 examples/README.md 참조. 전체 개발 환경: pip install -e ".[dev]" + npm installpython -m pytest tests/ -q (node+pyodide 있으면 뷰어 스모크 포함).

구성

파일 내용 수업 주제
loopsym/core.py Block 베이스 클래스, Diagram, 위상 정렬 + 대수 루프 검출 클래스/상속, 그래프 알고리즘
loopsym/blocks.py Step·Gain·Sum·Product·Integrator·TransferFunction·PyFunction·Scope + Subsystem(Inport/Outport, 마스크) + 이산 블록(UnitDelay·DiscreteIntegrator·DiscreteTF) + Mux/Demux 상태공간, feedthrough, 샘플링
loopsym/sim.py flatten(서브시스템 펼치기) + 고정 스텝 RK4 + 이산 상태 실행 수치적분, 안정성, 계층 구조
loopsym/viz.py + viewer.html 인터랙티브 뷰어 + save_json/load_json
loopsym/realtime.py 실시간 모드: run_realtime() — 절대 데드라인 + hybrid sleep 페이서, 지터 계측 실시간 시스템, 스케줄링
examples/ex5_realtime_demo.py P40 데모(이산 PI 50Hz)를 뷰어에서 라이브로 실시간 vs 배치
loopsym/ros2.py ROS2 블록: Ros2Subscribe/Ros2Publish — 다이어그램이 곧 rclpy 노드 미들웨어, ZOH/지연
examples/ex6_ros2_turtlesim.py turtlesim go-to-goal — 블록 제어기가 실제 ROS2 토픽 폐루프 실기체로 가는 다리
examples/ex1_pi_closed_loop.py PI 제어 폐루프 (2차 플랜트) 폐루프, 정상상태 오차 0
examples/ex2_pendulum_pyfunction.py 비선형 진자 + PD (PyFunction) 왜 적분항이 필요한가
examples/ex3_subsystem.py 마스크 PI 서브시스템 (Gain(k="$Kp")) 계층화, flatten, 마스크
examples/ex4_discrete_pi.py 연속 PI vs 이산 PI (Ts 영향) 샘플링, PX4 로 가는 다리
tests/ pytest 회귀 + Pyodide 스모크(뷰어 실행 경로 검증)
docs/ 15주 커리큘럼 설계, P14/P40 스파이크 결과
benchmarks/ 실시간 지터 재검증 (p40_realtime.py --hz --duration) + 결과 리포트
spikes/, hardware/ 최초 실시간 스파이크(폐기됨 — benchmarks 가 유효), Bebop 2 스파이크(+안전 체크리스트)

엔진 확장 모듈 (전부 순수 파이썬, PX4 v1.16 검증본 이식):

파일 내용
loopsym/drone.py 6-DoF 멀티로터 플랜트(프레임: quad X/+, hexa, octo) + PX4 Rate/Attitude/Position Controller + Mixer + 화이트박스 조립 블록
loopsym/sensors.py IMU — fidelity 규약(ideal/noisy, 포트 불변, 시드 고정)
loopsym/estimation.py ComplementaryFilter (Mahony)
loopsym/navigation.py WaypointManager (웨이포인트 순회)
loopsym/joystick.py 게임패드 입력 (브라우저 Gamepad API → 서버, RC Mode 2)
examples/ex7~ex10 드론 위치제어 / IMU fidelity / PX4 화이트박스 / 조이스틱 비행 — examples/README.md

다이어그램 뷰어 (render_html)

  • Simulink 식 도형: Gain ▷ 삼각형(값 내부 표시), Sum ○ 원(부호 표시), Product ×/÷, Integrator 1/s, TransferFunction 분수 표기, Scope 파형 아이콘
  • 블록 드래그, 출력→입력 포트 드래그로 와이어 연결/재연결, Delete 로 삭제
  • Simulink 식 직교 배선: 자동 배선이 다른 블록을 피해 가고, 와이어 가운데 세그먼트를 드래그하면 수동 배선(저장/열기 보존). 더블클릭 = 자동 복귀
  • Shift+클릭 다중 선택 → "서브시스템으로 묶기" (경계 Inport/Outport 자동 생성), 더블클릭으로 내부 진입(브레드크럼/Esc 로 상위), "풀기"로 해체
  • 우측 인스펙터에서 파라미터 수정 후 ▶ 실행 — Pyodide 가 파이썬 loopsym 엔진을 브라우저 안에서 그대로 실행 (아래 아키텍처 참조)
  • "Python 코드" 버튼: 현재 다이어그램을 (서브시스템 포함) loopsym 스크립트로 역생성
  • PyFunction 은 식 문자열("6.0*(u0-u1) - 2.0*u2")로 쓰면 뷰어에서도 실행 가능

뷰어 아키텍처 — 시뮬레이션 엔진은 파이썬 하나뿐 (2026-07 변경)

초기 뷰어는 파이썬 엔진을 JS 로 복제해 갖고 있었다(수동 동기화 + test_viewer_sync.py 로 검증). 블록이 늘 때마다 두 벌을 고쳐야 하고 어긋나면 "뷰어와 파이썬 결과가 다른" 최악의 교육 사고가 나므로, JS 엔진을 폐기하고 Pyodide 로 전환했다 (git 히스토리에 구현이 남아 있다 — "파이썬 엔진을 JS 로 포팅해 보기"는 심화 과제로 재사용 가능).

  • 뷰어 JS 의 역할: ① 블록 편집 ② 모델 JSON 직렬화 ③ Pyodide 로드 + loopsym.webapi.run_simulation(diagram_json, t_end, dt) 호출 ④ Scope 렌더링
  • render_html() 이 만드는 HTML 에는 엔진 소스(.py)가 심어져 있어 단독으로 동작한다. 원본 viewer.html 을 직접 열 때는 옆의 .py 를 fetch 하므로 repo 를 HTTP 로 서빙해야 한다 (python -m http.server).
  • 최초 실행 시 Pyodide 로딩 ~5초 (이후 재실행은 즉시). 오프라인 교실은 두 가지 경로: ① 로컬 서버 모드(python -m loopsym.server, 권장 — Pyodide 불필요) ② python tools/vendor_pyodide.py 로 로컬 사본 설치 후 repo 를 HTTP 로 서빙해서 열기(python -m http.server). file:// 로 직접 연 페이지는 브라우저 보안 때문에 vendored 사본을 못 쓰고 CDN 만 시도한다.
  • 검증: tests/test_pyodide_smoke.py 가 node + pyodide(npm) 로 뷰어와 같은 실행 경로를 헤드리스로 돌려 네이티브 엔진·회귀 기준값과 대조한다 (repo 루트에서 npm install 필요; CI 에서 항상 실행).

로컬 서버 모드 (python -m loopsym.server)

같은 뷰어가 로컬 파이썬 서버에 붙어 시뮬레이션을 돌리는 두 번째 모드. HILS(실제 Pixhawk 연결)와 실시간 러너는 브라우저 샌드박스 밖이 필요하므로 이 모드에서만 동작하게 된다.

python -m pip install "loopsym[server]"   # PyPI 설치 사용자 (fastapi + uvicorn)
# (소스 체크아웃이면: pip install -e ".[server]")
python -m loopsym.server                  # ws://localhost:8765
loopsym                                   # 뷰어를 열면 배지가 'Local Server'
  • 뷰어는 페이지 로드 때 자동 감지: 서버가 있으면 좌상단 배지가 Local Server, 없으면 Browser (Pyodide) (사용자 개입 없는 폴백). 서버를 나중에 켰다면 뷰어를 새로고침.
  • 서버 모드에서는 결과가 스트리밍되고(진행 중 Scope 가 갱신됨), ▶ 버튼이 ■ 정지로 바뀌어 긴 실행을 중단할 수 있다 (1초 내 반영).
  • 스레딩 원칙: physics never waits for the viewer — 시뮬 루프는 전용 스레드, 전송은 non-blocking queue(가득 차면 오래된 청크 폐기). 프로토콜/구조는 loopsym/server.py docstring 참조.
  • 두 모드는 같은 파이썬 엔진을 부르므로 결과가 동일하다 (tests/test_server.py 가 ex1~ex4 로 검증).

실시간 모드 (external mode) — [CLD-P42]

서버 모드에서는 "실시간" 체크박스가 나타난다. 켜고 실행하면 시뮬레이션 시간이 벽시계에 1:1 로 페이싱되어 Scope 가 라이브로 흐르고, 완료 시 상태줄에 지터 통계(p99/miss율)가 표시된다. 실기체 블록(Bebop/ROS2/MAVLink)과 HILS 는 전부 이 모드 위에 얹힌다.

from loopsym import run_realtime
stats = run_realtime(bd, t_end=10.0, dt=0.02)          # 50Hz, 벽시계 10초
stats = run_realtime(bd, t_end=10.0, dt=0.02, speed=2) # 2배속
# stats: p50/p95/p99/max/miss_pct/burst + stopped/elapsed_s
  • 수치 결과는 배치 run()비트 단위로 동일 — 페이싱은 스텝 사이의 대기일 뿐 적분에 관여하지 않는다 (tests/test_realtime.py 검증).
  • 스케줄러(절대 데드라인 + coarse sleep/fine busy-wait)는 P40 재검증에서 250Hz p99 4.002ms 를 실측한 그 코드다 (loopsym/realtime.py 공용).
  • 데모: python examples/ex5_realtime_demo.py → 서버 켜고 ex5_diagram.html 에서 "실시간" 체크 후 실행.

ROS2 블록 — [CLD-P44a]

Ros2Subscribe(토픽→스칼라, sample_time 마다 ZOH 스냅샷 = 한 샘플 지연)와 Ros2Publish(스칼라→토픽, major step 에서만 발행)로 다이어그램이 곧 ROS2 노드가 된다. rclpy 는 블록을 만들 때만 임포트되므로 코어 무의존성 그대로 (Pyodide 에서는 실행 시 안내 에러). 수신 콜백은 rclpy 스핀 스레드에서 래치만 갱신 — 제어 루프는 절대 블로킹하지 않는다.

python examples/ex6_ros2_turtlesim.py   # turtlesim 자동 기동 → go-to-goal
# 실측: 목표 도달 0.000m, 50Hz 지터 p99 20.16ms, miss 0%

검증: tests/test_ros2.py — 같은 다이어그램 안 Publish→DDS→Subscribe 루프백, defaults, JSON 라운드트립, major-step 단일 발행 (rclpy 없으면 skip).

실시간 성능 (실측, 2026-07-14)

250Hz 에서, 6-DoF 쿼드 물리(블록 그래프 경유) + MAVLink HIL_SENSOR 인코딩/송신 + WebSocket 10Hz 뷰어 스트리밍을 동시에 건 조건에서 p99 스텝 간격 4.002ms (주기 4ms, deadline miss 0.003%, 연속 miss ≤1; 일반 리눅스 데스크탑, GC 켠 상태, 5분 측정). 500Hz 에서도 p99 2.000ms. → 파이썬 벽시계 루프로 HILS 급 상위 제어 타당. 재현: python benchmarks/p40_realtime.py (조건·수치: benchmarks/results/20260714_p40.md).

설계 노트 (Simulink 와의 대응)

  • feedthrough: 출력이 현재 입력에 즉시 의존하면 True. Integrator / TransferFunction(strictly proper) 은 False — 이 블록들이 피드백 루프를 끊어 주며, 하나도 없이 닫힌 고리는 AlgebraicLoopError.
  • 실행 의미론: ① 상태 블록 출력(상태만으로 결정) → ② feedthrough 블록을 위상 순서로 → ③ 상태 미분 수집 → RK4. Simulink 의 minor/major step 구조의 단순화판.
  • 신호는 스칼라 하나로 제한 — 교육 명료성 원칙. 벡터량은 포트 여러 개로 펼치고, 시각 난잡함은 뷰어의 와이어 번들링(나란한 배선 ≥3 = 굵은선 ×N)이 해결한다. Mux/Demux 는 제한적 실험 기능 (docs/spike_p14 참조).

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

loopsym-0.1.1.tar.gz (106.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

loopsym-0.1.1-py3-none-any.whl (90.2 kB view details)

Uploaded Python 3

File details

Details for the file loopsym-0.1.1.tar.gz.

File metadata

  • Download URL: loopsym-0.1.1.tar.gz
  • Upload date:
  • Size: 106.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.12

File hashes

Hashes for loopsym-0.1.1.tar.gz
Algorithm Hash digest
SHA256 b3e9a57173359a28dae6946fbd91676939f78a42945723cf5a03eeef07d95af6
MD5 9bbb454ccc5699a46c934faba3ad8d01
BLAKE2b-256 af8897fdba4e727cac6cdabf59167b133cf7fba1258a73ffc811b3c23cca174d

See more details on using hashes here.

File details

Details for the file loopsym-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: loopsym-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 90.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.12

File hashes

Hashes for loopsym-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 203e6e3adaac5b2f5afbc2d944bebf47027dafe55b44715a4ddc74dea96a8bc4
MD5 9c864b33628cc9346c6a7bfeb19dee7d
BLAKE2b-256 5f952ab698d001de6b51709bc11d7a812887cb062008003d460bdccbdf8067cf

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page