From c5cfbdc67f89e79156c9fb52ff459f83db6eb0a7 Mon Sep 17 00:00:00 2001
From: Radek Oslejsek <oslejsek@fi.muni.cz>
Date: Fri, 26 Feb 2021 10:27:47 +0100
Subject: [PATCH] Efficient concurrent thread-safe implementation of
 HausdorffDistMeshVisitor

---
 .../mesh/HausdorffDistMeshVisitor.java        | 76 ++++++++++++++++---
 .../cz/fidentis/analyst/mesh/MeshVisitor.java | 29 ++++---
 2 files changed, 86 insertions(+), 19 deletions(-)

diff --git a/Comparison/src/main/java/cz/fidentis/analyst/visitors/mesh/HausdorffDistMeshVisitor.java b/Comparison/src/main/java/cz/fidentis/analyst/visitors/mesh/HausdorffDistMeshVisitor.java
index f1f0700b..237cae9a 100644
--- a/Comparison/src/main/java/cz/fidentis/analyst/visitors/mesh/HausdorffDistMeshVisitor.java
+++ b/Comparison/src/main/java/cz/fidentis/analyst/visitors/mesh/HausdorffDistMeshVisitor.java
@@ -8,14 +8,25 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 
 /**
  * Visitor for Hausdorff distance. 
  * This visitor is instantiated on a single mesh facet or multiple facets. 
  * When applied to other facets, it computes Huasdorff distance to them.
+ * <p>
+ * This visitor is thread-safe. A single instance of the visitor can be used 
+ * to inspect multiple mesh models or facets simultaneously.
+ * </p>
  * 
  * @author Matej Lukes
  * @author Radek Oslejsek
@@ -23,7 +34,7 @@ import java.util.Set;
 public class HausdorffDistMeshVisitor extends MeshVisitor {
     
     private boolean relativeDistance;
-    private Map<MeshFacet, List<Double>> distances = new HashMap<>();
+    private final Map<MeshFacet, List<Double>> distances = new HashMap<>();
     
     /**
      * @param mainFacets Facets of which distance to other facets is computed. Must not be {@code null}
@@ -31,7 +42,7 @@ public class HausdorffDistMeshVisitor extends MeshVisitor {
      * to the normal vectors of source facets (normal vectors have to present), 
      * i.e., we can get negative distances.
      * @param concurrently If {@code true} and this visitor is thread-safe, then
-     * the visitor is applied concurrently on multiple mesf facets.
+     * the visitor is applied concurrently when inspecting multiple mesh facets.
      * @throws IllegalArgumentException if some parametr is wrong
      */
     public HausdorffDistMeshVisitor(Set<MeshFacet> mainFacets, boolean relativeDistance, boolean concurrently) {
@@ -51,7 +62,7 @@ public class HausdorffDistMeshVisitor extends MeshVisitor {
      * to the normal vectors of source facets (normal vectors have to present), 
      * i.e., we can get negative distances.
      * @param concurrently If {@code true} and this visitor is thread-safe, then
-     * the visitor is applied concurrently on multiple mesf facets.
+     * the visitor is applied concurrently when inspecting multiple mesh facets.
      * @throws IllegalArgumentException if some parametr is wrong
      */
     public HausdorffDistMeshVisitor(MeshFacet mainFacet, boolean relativeDistance, boolean concurrently) {
@@ -68,7 +79,7 @@ public class HausdorffDistMeshVisitor extends MeshVisitor {
      * to the normal vectors of source facets (normal vectors have to present), 
      * i.e., we can get negative distances.
      * @param concurrently If {@code true} and this visitor is thread-safe, then
-     * the visitor is applied concurrently on multiple mesf facets.
+     * the visitor is applied concurrently when inspecting multiple mesh facets.
      * @throws IllegalArgumentException if some parametr is wrong
      */
     public HausdorffDistMeshVisitor(MeshModel mainModel, boolean relativeDistance, boolean concurrently) {
@@ -80,18 +91,65 @@ public class HausdorffDistMeshVisitor extends MeshVisitor {
     
     @Override
     protected void visitMeshFacet(MeshFacet comparedFacet) {
+        int threads = Runtime.getRuntime().availableProcessors();
+        ExecutorService executor = Executors.newFixedThreadPool(threads);
+        List<Future<MeshVisitor>> results = new LinkedList<>();
+        
         for (Map.Entry<MeshFacet, List<Double>> entry: distances.entrySet()) {
             List<MeshPoint> vertices = entry.getKey().getVertices();
             List<Double> distList = entry.getValue();
             
-            boolean firstComparison = distList.isEmpty();
+            if (concurrently()) {
+                for (int i = 0; i < vertices.size(); i++) {
+                    Point2MeshVisitor visitor = new Point2MeshVisitor(vertices.get(i), relativeDistance, true);
+                    comparedFacet.accept(visitor);
+                    Future<MeshVisitor> result = executor.submit(visitor); // fork and continue
+                    results.add(result);
+                }
+                updateFacetDistancesConcurrently(distList, results);
+            } else {
+                updateFacetDistancesSequentially(vertices, distList, comparedFacet);
+            }
+        }
+    }
+    
+    /**
+     * Sequentially updates distances of particular mesh. The update has to be 
+     * exclusive (sychronized with multiple threads)
+     */
+    protected synchronized void updateFacetDistancesSequentially(
+            List<MeshPoint> facetVertices, 
+            List<Double> facetDistances, 
+            MeshFacet comparedFacet) {
+        
+        boolean firstComparison = facetDistances.isEmpty();
             
-            for (int i = 0; i < vertices.size(); i++) {
-                Point2MeshVisitor visitor = new Point2MeshVisitor(vertices.get(i), relativeDistance, concurrently());
-                comparedFacet.accept(visitor);
+        for (int i = 0; i < facetVertices.size(); i++) {
+            Point2MeshVisitor visitor = new Point2MeshVisitor(facetVertices.get(i), relativeDistance, false);
+            comparedFacet.accept(visitor);
+            double dist = visitor.getDistance();
+            updateDistances(facetDistances, firstComparison, dist, i);
+        }
+    }
+    
+    /**
+     * Sequentially updates distances of particular mesh. The update has to be 
+     * exclusive (sychronized with multiple threads)
+     */
+    protected synchronized void updateFacetDistancesConcurrently(
+            List<Double> facetDistances, 
+            List<Future<MeshVisitor>> results) {
+        
+        boolean firstComparison = facetDistances.isEmpty();
+
+        try {
+            for (int i = 0; i < results.size(); i++) { // wait ntil all computations are finished
+                Point2MeshVisitor visitor = (Point2MeshVisitor) results.get(i).get(); 
                 double dist = visitor.getDistance();
-                updateDistances(distList, firstComparison, dist, i);
+                updateDistances(facetDistances, firstComparison, dist, i);
             }
+        } catch (InterruptedException | ExecutionException ex) {
+            Logger.getLogger(HausdorffDistMeshVisitor.class.getName()).log(Level.SEVERE, null, ex);
         }
     }
     
diff --git a/MeshModel/src/main/java/cz/fidentis/analyst/mesh/MeshVisitor.java b/MeshModel/src/main/java/cz/fidentis/analyst/mesh/MeshVisitor.java
index 12315780..85f962a4 100644
--- a/MeshModel/src/main/java/cz/fidentis/analyst/mesh/MeshVisitor.java
+++ b/MeshModel/src/main/java/cz/fidentis/analyst/mesh/MeshVisitor.java
@@ -10,11 +10,14 @@ import java.util.concurrent.Callable;
  * Implement this interface whenever you want to define new algorithm over a mesh.
  * </p>
  * <p>
- * There are two visitation strategies available.
- * The {@link cz.fidentis.analyst.mesh.mesh.MeshVisitor#visitMeshFacet} inspects
- * the facet immediately. On the contrary, 
- * the {@link cz.fidentis.analyst.mesh.MeshVisitor#visitConcurrently} enable to 
- * inspect multiple facets concurretly. The later has to be called from a code like this:
+ * The visitor can be instatiated either as sequential and concurrent. 
+ * The {@link cz.fidentis.analyst.mesh.mesh.MeshVisitor#visit} inspection method
+ * of a sequential visitor is applied immediately. On the contrary, if the
+ * visitor is concurrent and thread-safe, then the methed only stores the inpected
+ * mesh facet for later concurrent inspection. To trigger the facet inspection,
+ * a {@link java.util.concurrent.ExecutorService} has to be used to call
+ * the {@link cz.fidentis.analyst.mesh.mesh.MeshVisitor#call} method asynchronously
+ * and then wait for results of all triggered visitors. 
  * </p>
  * 
  * @author Radek Oslejsek
@@ -32,8 +35,12 @@ public abstract class MeshVisitor implements Callable<MeshVisitor> {
     
     /**
      *
-     * @param concurrently If {@code true} and this visitor is thread-safe, then
-     * the visitor is applied concurrently on multiple mesh facets.
+     * @param concurrently If {@code true} and the visitor is thread-safe, then 
+     * the visitor is created as concurent
+     * (the {@link cz.fidentis.analyst.mesh.MeshVisitor#visit} is not executed 
+     * immediately byt postponed for concurrent asynchronous execution). 
+     * Otherwise, the visitor is created as sequential 
+     * (the {@link cz.fidentis.analyst.mesh.MeshVisitor#visit} is executed immediately.)
      */
     public MeshVisitor(boolean concurrently) {
         this.concurrently = concurrently;
@@ -41,9 +48,9 @@ public abstract class MeshVisitor implements Callable<MeshVisitor> {
     
     /**
      * Returns {@code true} if the implementation is thread-safe and then
-     * a single visitor instance can be applied to multiple mesh facets simultaneously.
+     * <b>a single visitor instance</b> can be applied to multiple mesh facets simultaneously.
      * If the visitor is not thread-safe, the concurrent inspection 
-     * via the {@link cz.fidentis.analyst.mesh.MeshVisitor#visitConcurrently}
+     * via the {@link cz.fidentis.analyst.mesh.MeshVisitor#visit}
      * is still possible, but with multiple visitor instances - one for each facet.
      * <p>
      * Thread-safe implementation means that any read or write from/to the visitor's 
@@ -75,7 +82,9 @@ public abstract class MeshVisitor implements Callable<MeshVisitor> {
     protected abstract void visitMeshFacet(MeshFacet facet);
     
     /**
-     * The main visitation method
+     * The main visitation method, either invoked immediately (if the visitor 
+     * was created a sequentional) or postponed to later parallel execution 
+     * (if the visitor is thread-safe and has been created a concurrent).
      * 
      * @param facet Mesh facet to be visited.
      */
-- 
GitLab