diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..d64cd49 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.jar Binary files differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..1af9e09 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 1b6c787..1aa94a4 100644 --- a/gradlew +++ b/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,13 +80,11 @@ esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,22 +131,29 @@ fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -193,11 +198,15 @@ done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ @@ -205,6 +214,12 @@ org.gradle.wrapper.GradleWrapperMain \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/gradlew.bat b/gradlew.bat index 107acd3..93e3f59 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +41,7 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -75,13 +76,15 @@ :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/src/main/java/ru/mcs/q/Tetrahedron.java b/src/main/java/ru/mcs/q/Tetrahedron.java index 08b422b..f42be91 100644 --- a/src/main/java/ru/mcs/q/Tetrahedron.java +++ b/src/main/java/ru/mcs/q/Tetrahedron.java @@ -10,6 +10,7 @@ private final Map faces; private final Map connections; private double energyLevel; + private volatile double pendingDelta = 0.0; public Tetrahedron(String id) { this.id = id; @@ -96,4 +97,25 @@ return String.format("Тетраэдр[%s] энергия=%.2f соединения=%s", id, energyLevel, connections.keySet()); } + + /** + * Фаза 1: вычислить дельту по градиенту соседей (НЕ применять). + * Вызывать для всех тетраэдров ДО applyDelta(). + */ + public void computeGradientFlow(double flowRate) { + double inflow = 0.0; + for (Tetrahedron neighbor : connections.values()) { + inflow += (neighbor.energyLevel - this.energyLevel) * flowRate; + } + this.pendingDelta = inflow; + } + + /** + * Фаза 2: применить ранее вычисленную дельту. + * Вызывать ПОСЛЕ того как computeGradientFlow вызван у всех. + */ + public void applyDelta(double damping) { + energyLevel = Math.max(0, energyLevel * damping + pendingDelta); + pendingDelta = 0; + } } \ No newline at end of file diff --git a/src/main/java/ru/mcs/q/field/QuantumFieldMesh.java b/src/main/java/ru/mcs/q/field/QuantumFieldMesh.java index 644c24a..537f7e7 100644 --- a/src/main/java/ru/mcs/q/field/QuantumFieldMesh.java +++ b/src/main/java/ru/mcs/q/field/QuantumFieldMesh.java @@ -9,6 +9,9 @@ private final Map tetrahedrons; private final ScheduledExecutorService scheduler; private boolean isActive; + private static final double FLOW_RATE = 0.30; // 30% градиента за тик + private static final double DAMPING = 0.999; + private int tickCount = 0; public QuantumFieldMesh() { this.tetrahedrons = new ConcurrentHashMap<>(); @@ -49,17 +52,17 @@ } private void updateField() { - System.out.println("\n=== Обновление квантового поля ==="); + System.out.println("\n=== Tick #" + tickCount++ + " ==="); - // Обновляем все тетраэдры - tetrahedrons.values().forEach(tetrahedron -> { - tetrahedron.oscillate(); - System.out.println(tetrahedron); + // Фаза 1: все вычисляют дельту одновременно (нет зависимости от порядка) + tetrahedrons.values().forEach(t -> t.computeGradientFlow(FLOW_RATE)); + + // Фаза 2: все применяют + tetrahedrons.values().forEach(t -> { + t.applyDelta(DAMPING); + System.out.println(t); }); - // Спонтанные соединения - attemptSpontaneousConnections(); - printFieldState(); } diff --git a/src/main/java/ru/mcs/q/grid/GridField.java b/src/main/java/ru/mcs/q/grid/GridField.java new file mode 100644 index 0000000..7258ea2 --- /dev/null +++ b/src/main/java/ru/mcs/q/grid/GridField.java @@ -0,0 +1,92 @@ +package ru.mcs.q.grid; + +import java.util.concurrent.*; + +public class GridField { + + public static final int WIDTH = 100; + public static final int HEIGHT = 100; + + // FLOW_RATE < 0.25 — условие устойчивости для 4-связного Лапласиана + private static final double FLOW_RATE = 0.24; + private static final double DAMPING = 0.999; // лёгкое затухание (энтропия) + + private final double[][] energy = new double[HEIGHT][WIDTH]; + private final double[][] buffer = new double[HEIGHT][WIDTH]; + private final double[][] display = new double[HEIGHT][WIDTH]; // снимок для рендера + + private final ScheduledExecutorService scheduler = + Executors.newSingleThreadScheduledExecutor(); + + private volatile int tick = 0; + + /** + * Добавить возбуждение в узел (x, y). + * Можно вызывать из любого потока. + */ + public void excite(int x, int y, double amount) { + synchronized (energy) { + if (x >= 0 && x < WIDTH && y >= 0 && y < HEIGHT) { + energy[y][x] = Math.max(0, energy[y][x] + amount); + } + } + } + + public void start(long tickMs) { + scheduler.scheduleAtFixedRate(this::update, 0, tickMs, TimeUnit.MILLISECONDS); + } + + public void stop() { + scheduler.shutdown(); + } + + /** + * Один тик симуляции. + * Правило: discrete Laplacian (уравнение теплопроводности): + * new_e = old_e * DAMPING + FLOW_RATE * (Σ_neighbors - 4 * old_e) + * + * Топология: ТОРИЧЕСКАЯ — граница = граница с противоположной стороны. + * Это и есть "закольцованная вселенная". + */ + private void update() { + synchronized (energy) { + for (int y = 0; y < HEIGHT; y++) { + for (int x = 0; x < WIDTH; x++) { + double center = energy[y][x]; + + // 4 соседа с wraparound (торус) + double left = energy[y][(x - 1 + WIDTH) % WIDTH]; + double right = energy[y][(x + 1) % WIDTH]; + double up = energy[(y - 1 + HEIGHT) % HEIGHT][x]; + double down = energy[(y + 1) % HEIGHT][x]; + + double laplacian = left + right + up + down - 4.0 * center; + buffer[y][x] = Math.max(0, center * DAMPING + FLOW_RATE * laplacian); + } + } + // Атомарный своп буферов + for (int y = 0; y < HEIGHT; y++) { + System.arraycopy(buffer[y], 0, energy[y], 0, WIDTH); + System.arraycopy(energy[y], 0, display[y], 0, WIDTH); + } + tick++; + } + } + + public double getDisplay(int x, int y) { return display[y][x]; } + public int getTick() { return tick; } + + public double getTotalEnergy() { + double sum = 0; + for (double[] row : display) + for (double v : row) sum += v; + return sum; + } + + public double getMaxEnergy() { + double max = 0; + for (double[] row : display) + for (double v : row) if (v > max) max = v; + return max; + } +} \ No newline at end of file diff --git a/src/main/java/ru/mcs/q/grid/GridMain.java b/src/main/java/ru/mcs/q/grid/GridMain.java new file mode 100644 index 0000000..2aba12c --- /dev/null +++ b/src/main/java/ru/mcs/q/grid/GridMain.java @@ -0,0 +1,37 @@ +package ru.mcs.q.grid; + +import javax.swing.*; + +public class GridMain { + public static void main(String[] args) { + System.out.println("🌌 Quantum Field — 10,000 узлов"); + System.out.println("Топология : ТОРУС (закольцованная вселенная)"); + System.out.println("Правило : ΔE = FLOW_RATE * Laplacian(E) [уравнение теплопроводности]"); + System.out.println("FLOW_RATE = 0.24 | DAMPING = 0.999/tick | 20 тиков/сек"); + System.out.println(); + + GridField field = new GridField(); + + // Одна точка возбуждения — смотрим как волна расходится + field.excite(50, 50, 10000.0); + System.out.println("✦ Начальное возбуждение: (50, 50) E=10000"); + System.out.println(" Наблюдайте: волна уйдёт к краям, обогнёт тор и вернётся."); + System.out.println(" При встрече двух фронтов — интерференционный узор."); + + // Можно раскомментировать для сразу двух источников (интерференция): + // field.excite(25, 50, 10000.0); + + field.start(50); + + SwingUtilities.invokeLater(() -> { + JFrame frame = new JFrame( + "Quantum Field — 10,000 Nodes — Gradient Propagation (Toroidal)"); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.setResizable(false); + frame.add(new GridVisualization(field)); + frame.pack(); + frame.setLocationRelativeTo(null); + frame.setVisible(true); + }); + } +} \ No newline at end of file diff --git a/src/main/java/ru/mcs/q/grid/GridVisualization.java b/src/main/java/ru/mcs/q/grid/GridVisualization.java new file mode 100644 index 0000000..db9f25c --- /dev/null +++ b/src/main/java/ru/mcs/q/grid/GridVisualization.java @@ -0,0 +1,100 @@ +package ru.mcs.q.grid; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; + +public class GridVisualization extends JPanel { + + private static final int CELL = 8; // пикселей на узел → 800×800 + private static final int INFO_H = 50; + + private final GridField field; + + public GridVisualization(GridField field) { + this.field = field; + setPreferredSize(new Dimension( + GridField.WIDTH * CELL, + GridField.HEIGHT * CELL + INFO_H)); + setBackground(Color.BLACK); + + // ~20 fps + new Timer(50, e -> repaint()).start(); + + // ЛКМ = добавить энергию, ПКМ = убрать + MouseAdapter mouse = new MouseAdapter() { + @Override public void mousePressed(MouseEvent e) { addEnergy(e); } + @Override public void mouseDragged(MouseEvent e) { addEnergy(e); } + + private void addEnergy(MouseEvent e) { + int cx = e.getX() / CELL; + int cy = e.getY() / CELL; + double amt = SwingUtilities.isLeftMouseButton(e) ? 2000.0 : -1000.0; + field.excite(cx, cy, amt); + } + }; + addMouseListener(mouse); + addMouseMotionListener(mouse); + } + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + Graphics2D g2 = (Graphics2D) g; + + double maxE = field.getMaxEnergy(); + if (maxE < 1e-9) maxE = 1.0; + + for (int y = 0; y < GridField.HEIGHT; y++) { + for (int x = 0; x < GridField.WIDTH; x++) { + double t = Math.max(0, Math.min(1, field.getDisplay(x, y) / maxE)); + g2.setColor(heatColor(t)); + g2.fillRect(x * CELL, y * CELL, CELL, CELL); + } + } + + // Инфо-панель + g2.setColor(new Color(15, 15, 15)); + g2.fillRect(0, GridField.HEIGHT * CELL, GridField.WIDTH * CELL, INFO_H); + + g2.setFont(new Font("Monospaced", Font.PLAIN, 13)); + g2.setColor(Color.WHITE); + g2.drawString(String.format( + "Tick: %6d | Nodes: %,d | ΣE: %,.1f | max: %.4f", + field.getTick(), + GridField.WIDTH * GridField.HEIGHT, + field.getTotalEnergy(), + field.getMaxEnergy()), + 8, GridField.HEIGHT * CELL + 18); + + g2.setColor(Color.GRAY); + g2.drawString( + "LMB / drag = +энергия RMB = -энергия Topology: TORUS", + 8, GridField.HEIGHT * CELL + 36); + } + + /** + * Тепловая шкала: чёрный → синий → голубой → жёлтый → красный → белый + */ + private Color heatColor(double t) { + float[][] keys = { + {0f, 0f, 0f}, // 0.0 чёрный + {0f, 0f, 1f}, // 0.2 синий + {0f, 1f, 1f}, // 0.4 голубой + {1f, 1f, 0f}, // 0.6 жёлтый + {1f, 0f, 0f}, // 0.8 красный + {1f, 1f, 1f}, // 1.0 белый + }; + double pos = t * (keys.length - 1); + int seg = (int) Math.min(pos, keys.length - 2); + double frac = pos - seg; + float r = lerp(keys[seg][0], keys[seg + 1][0], frac); + float gv = lerp(keys[seg][1], keys[seg + 1][1], frac); + float b = lerp(keys[seg][2], keys[seg + 1][2], frac); + return new Color(r, gv, b); + } + + private float lerp(float a, float b, double t) { + return (float)(a + (b - a) * t); + } +} \ No newline at end of file