Диссертация (1149786), страница 14
Текст из файла (страница 14)
Поэтому он никак не отражаетсяна наведенной триангуляции. И, наконец, третий шаг соответствует повторному применению процедуры укрупнения триангуляции.Таким образом, можно считать, что рассмотренный способ построенияи укрупнения симплициального подразделения в целом соответствует двухшаговому укрупнению триангуляции специального вида, рассмотренному в разделе 1.6.864Компьютерное моделирование задачипостроения симплициального подразделенияспециального видаКак известно, ключевым моментом в проектировании програмного обеспечения является определение программного интерфейса, с одной стороны достаточного для решения прикладных задач, и с другой стороны не являющегосяизбыточным.
Общим подходом, используемым производимом в данной работемоделировании, будет являться проектирование потоков данных с возможностью их модификации по мере прохождения через поток. Соответственно, будутпредоставлены классы для описания источников данных и классы-потребителиданных, что обеспечит гибкость программных конструкций.4.1Базовый интерфейс источника данныхБазовой конструкцией нашей компьютерной модели будет являться интерфейс,задающий абстрактное симплициальное подразделениеpackage org.ivg;import java.util.stream.Stream;public interface ISimplicialDivision {/*** Access to separate nodes of the division.* The produced vertices are distinct.* No specific order is guaranteed, however for infinite* streams, vertices with lower coordinates normally come* first.*/Stream<IPoint> vertices();/*** Stream of edges that are formed with a pair of nodes.*/Stream<IEdge> edges();87/*** A whole-simplex stream.* These should be used to determine if the simplex makes* its way to the destination division.*/Stream<ISimplex> simplices();/*** For the given simplex returns a set of adjacent* simplices.
Allows to consider the division as a graph.*/Stream<ISimplex> adjacent(ISimplex simplex);}Данный интерфейс описывает набор методов, предоставляющих доступк потокам-источникам данных. Первый из них, simplices() описывает потокструктур, описывающих составляющие части симплициального подразделения.Два других метода, vertices() и edges(), предоставляют доступ к потокуисточнику вершин и одномерных граней симплексов соответственно.
Следуетотметить, что результат первого метода должен ограничивать результаты работы последних двух методов. Каждый конкретный наследник абстрактногобазового интерфейса ISimplicialDivision должен предоставить свою реализацию указанных методов, которые могут либо предоставлять доступ к ужесозданным и хранимым в памяти компьютера массивам данных, либо генерировать данные «на лету».
Последний метод интерфейса, adjacent(), позволяетрассматривать симплициальное подразделение как граф, в котором узлами являются симплексы, а дуги соединяют симплексы, имеющие общую грань.Полезной особенностью технологии Streams является отложенность вычислений, в том числе, и запросов к источнику за очередной порцией данных.Это позволяет создать виртуально-бесконечный источник вершин и граней симплициального подразделения, который будет затем ограничен внешними дополнительными условиями.4.2Элементарные типы данныхКак видно из интерфейса ISimplicialDivision, для представления узлов и граней используются типы данных, наследуемые от IPoint и IEdge.
Эти типы88также являются абстрактными интерфейсами.package org.ivg;public interface IPoint {int dimensions();int coordinate(int iAxis);default int x() {return coordinate(0);}default int y() {return coordinate(1);}default int z() {return coordinate(2);}}Для каждого наследника класса точки есть только два обязательных дляреализации метода.
Метод dimensions() возвращает число n — размерностьпространства, для точки заданной в Rn . Метод coordinate() используется длядоступа к отдельным координатам точки по логическому номеру координатнойоси из диапазона [0, n − 1]. По соглашению, и для удобства использования,координатным осям 0, 1 и 2 соответствуют имена X, Y и Z, что реализованокак соответствующие методы с реализацией по-умолчанию.Отметим, что объекты конкретных реализаций интерфейса IPoint предполагаются неизменными (immutable).package org.ivg;import java.util.function.Predicate;public interface IEdge {IPoint firstPoint();IPoint secondPoint();89default boolean allPointsCondition(Predicate<? super IPoint> pred) {return pred.test(firstPoint()) &&pred.test(secondPoint());}default boolean anyPointCondition(Predicate<? super IPoint> pred) {return pred.test(firstPoint()) ||pred.test(secondPoint());}}Абстрактный тип данных IEdge задает пару точек, что и выражаетсяметодами firstPoint() и secondPoint().Для удобства проверки того, что точки грани удовлетворяют тому илииному условию, в интерфейс включены метод allPointsCond(), возвращающий true, когда условие задаваемое аргументом метода выполняется для обеихточек, и false в противном случае.
Другой метод, anyPointsCond(), позволяетопределить, когда задаваемое условие выполняется хотя бы для одной из двухточек.Конкретные реализации базового интерфейса IPoint включают представления для двумерной точки Point2D и трехмерной точки Point3D.package org.ivg;import java.util.Arrays;public class Point2D implements IPoint {int[] coords;public Point2D(int x, int y) {this.coords = new int[] {x, y};}@Overridepublic int dimensions() {return 2;90}@Overridepublic int coordinate(int iAxis) {if (iAxis < 0 || iAxis >= 2) {throw new IndexOutOfBoundsException();}return coords[iAxis];}@Overridepublic boolean equals(Object obj) {if (obj instanceof Point2D) {Point2D other = (Point2D)obj;return Arrays.equals(coords, other.coords);}return false;}@Overridepublic int hashCode() {return Arrays.hashCode(coords);}}Данная реализация достаточно очевидна и не требует лишних пояснений.Помимо специфической для данного приложения функциональности, потребовалось также предоставить перегруженные реализации пары методов equals()и hashCode(), необходимых для того, чтобы сравнение однотипных объектовпри помещении их в хэш-таблицу производилось корректно.
Трехмерная точказадается классом Point3D и отличается от Point2D только лишь дополнительной координатой с номером 2, которую, по соглашению, мы ассоциируем с осьюZ.package org.ivg;import java.util.Arrays;public class Point3D implements IPoint {int[] coords;91public Point3D(int x, int y, int z) {this.coords = new int[] {x, y, z};}@Overridepublic int dimensions() {return 3;}@Overridepublic int coordinate(int iAxis) {if (iAxis < 0 || iAxis >= 3) {throw new IndexOutOfBoundsException();}return coords[iAxis];}@Overridepublic boolean equals(Object obj) {if (obj instanceof Point3D) {Point3D other = (Point3D)obj;return Arrays.equals(coords, other.coords);}return false;}@Overridepublic int hashCode() {return Arrays.hashCode(coords);}}Можно заметить, что не представляет особого труда определить общуюреализацию точки произвольного конечномерного эвклидова пространства.
Дляцелей данной модельной проверки, однако, это представляется излишним, таккак такое обобщение привело бы к потере читаемости кода.Класс Edge2D представляет собой конкретную реализацию определенноговыше интерфейса IEdge, для задания ребра симплекса в двумерном пространстве. Объекты этого класса неизменяемые.92package org.ivg;public class Edge2D implements IEdge {private Point2D firstPoint;private Point2D secondPoint;public Edge2D(int firstX, int firstY, int secondX,int secondY) {this.firstPoint = new Point2D(firstX, firstY);this.secondPoint = new Point2D(secondX, secondY);}public Edge2D(Point2D firstPoint, Point2D secondPoint) {this.firstPoint = firstPoint;this.secondPoint = secondPoint;}@Overridepublic IPoint firstPoint() {return firstPoint;}@Overridepublic IPoint secondPoint() {return secondPoint;}@Overridepublic boolean equals(Object obj) {if (obj instanceof Edge2D) {Edge2D other = (Edge2D)obj;return (firstPoint.equals(other.firstPoint) &&secondPoint.equals(other.secondPoint)) ||(firstPoint.equals(other.secondPoint) &&secondPoint.equals(other.firstPoint));}return false;}@Overridepublic int hashCode() {93return firstPoint.hashCode() ^ secondPoint.hashCode();}}Класс Edge3D лишь незначительно отличается от Edge2D добавлением дополнительной координаты Z и использованием трехмерных точек для храненияконцов ребра.package org.ivg;public class Edge3D implements IEdge {private Point3D firstPoint;private Point3D secondPoint;public Edge3D(int firstX, int firstY, int firstZ,int secondX, int secondY, int secondZ) {this.firstPoint = new Point3D(firstX, firstY,firstZ);this.secondPoint = new Point3D(secondX, secondY,secondZ);}public Edge3D(Point3D firstPoint, Point3D secondPoint) {this.firstPoint = firstPoint;this.secondPoint = secondPoint;}@Overridepublic IPoint firstPoint() {return firstPoint;}@Overridepublic IPoint secondPoint() {return secondPoint;}@Overridepublic boolean equals(Object obj) {if (obj instanceof Edge3D) {94Edge3D other = (Edge3D)obj;return (firstPoint.equals(other.firstPoint) &&secondPoint.equals(other.secondPoint)) ||(firstPoint.equals(other.secondPoint) &&secondPoint.equals(other.firstPoint));}return false;}@Overridepublic int hashCode() {return firstPoint.hashCode() ^ secondPoint.hashCode();}}4.3Генерация исходного подразделенияСледующим шагом в построении компьютерной модели будет являться предоставление класса-реализации базового интерфейса ISimplicialDivision.
Первым таким классом является InfiniteAreaDivision, имплементирующий указанный интерфейс. Он реализует возможность создания виртуально-бесконечного потока данных, представляющих либо вершины симплициального подразделения (метод, называющийся vertices()), либо одномерные ребра симплексов (метод edges()).
Создание потока данных обеспечивается путем реализациигенерирующей функции, реализующей концепцию сплитератора [49, 66] (в оригинальной транскрипции Spliterator), являющуюся по сути внутренним итератором по абстрактной коллекции данных. В языке Java 8 [65], нет встроенного простого способа ограничить бесконечный поток, задав условие-предикатпризнака окончания генерации, поэтому приходится либо использовать ограничения, задающие объем генерируемых данных, например, с помощью методаStream.limit(long size), либо предоставлять собственный вариант реализации интерфейса Stream, имеющий необходимую функциональность. В версииязыка Java 9 [68], появилась подходящая функция Stream.takeWhile(), однако эта ревизия языка, на момент написания данной работы, все еще находитсяв стадии разработки.При обращении за очередной порцией данных к объекту-сплитератору95возможны два сценария: либо среда исполнения запросит очередной единичный элемент потока данных, либо поступит запрос на разделение всего потокана пару под-потоков для целей их независимой обработки.