Merge branch 'develop' into feature/JAL-4159_pasimap
authorJim Procter <jprocter@dundee.ac.uk>
Thu, 16 May 2024 07:07:17 +0000 (08:07 +0100)
committerJim Procter <jprocter@dundee.ac.uk>
Thu, 16 May 2024 07:07:17 +0000 (08:07 +0100)
doesn't seem to have apache-math present
 Conflicts:
build.gradle

40 files changed:
.gitignore
.zip [new file with mode: 0644]
appletlib.zip [new file with mode: 0644]
build.diff [new file with mode: 0644]
build.gradle
help/markdown/releases/release-2_11_3_0.md
j11lib/commons-math3-3.6.1.jar [new file with mode: 0644]
j8lib/commons-math3-3.6.1.jar [new file with mode: 0644]
resources/lang/Messages.properties
resources/lang/Messages_es.properties
src/jalview/analysis/AlignSeq.java
src/jalview/analysis/Connectivity.java [new file with mode: 0644]
src/jalview/analysis/ConnectivityException.java [new file with mode: 0644]
src/jalview/analysis/Finder.java
src/jalview/analysis/PaSiMap.java [new file with mode: 0755]
src/jalview/analysis/ccAnalysis.java [new file with mode: 0755]
src/jalview/gui/AlignFrame.java
src/jalview/gui/CalculationChooser.java
src/jalview/gui/CutAndPasteTransfer.java
src/jalview/gui/Desktop.java
src/jalview/gui/FontChooser.java
src/jalview/gui/IProgressIndicator.java
src/jalview/gui/PCAPanel.java
src/jalview/gui/PaSiMapPanel.java [new file with mode: 0644]
src/jalview/gui/PairwiseAlignPanel.java
src/jalview/gui/ProgressBar.java
src/jalview/gui/RotatableCanvas.java
src/jalview/gui/StructureChooser.java
src/jalview/gui/WebserviceInfo.java
src/jalview/jbgui/GCutAndPasteTransfer.java
src/jalview/jbgui/GPCAPanel.java
src/jalview/jbgui/GPaSiMapPanel.java [new file with mode: 0755]
src/jalview/math/Matrix.java
src/jalview/math/MatrixI.java
src/jalview/math/MiscMath.java [new file with mode: 0755]
src/jalview/math/SameLengthException.java [new file with mode: 0644]
src/jalview/viewmodel/AlignmentViewport.java
src/jalview/viewmodel/PCAModel.java
src/jalview/viewmodel/PaSiMapModel.java [new file with mode: 0644]
test/jalview/analysis/AlignSeqTest.java

index 59c4a99..4a81616 100644 (file)
@@ -17,6 +17,7 @@ TESTNG
 /jalviewApplet.jar
 /benchmarking/lib
 *.class
+*.patch
 /site
 /site-resources
 /libjs
diff --git a/.zip b/.zip
new file mode 100644 (file)
index 0000000..4de119f
Binary files /dev/null and b/.zip differ
diff --git a/appletlib.zip b/appletlib.zip
new file mode 100644 (file)
index 0000000..f089e5e
Binary files /dev/null and b/appletlib.zip differ
diff --git a/build.diff b/build.diff
new file mode 100644 (file)
index 0000000..8dc92f7
--- /dev/null
@@ -0,0 +1,531 @@
+--- build.gradle       2023-07-02 09:21:09.216542293 +0200
++++ ../jalview_new/build.gradle        2023-05-15 19:20:46.892861180 +0200
+@@ -45,8 +45,8 @@
+   id 'java'
+   id 'application'
+   id 'eclipse'
+-  id "com.diffplug.gradle.spotless" version "3.28.0"
+-  id 'com.github.johnrengelman.shadow' version '4.0.3'
++  id "com.diffplug.spotless" version "6.18.0" //.gradle.spotless" "3.28.0"
++  id 'com.github.johnrengelman.shadow' version '8.1.1'        // was 4.0.3
+   id 'com.install4j.gradle' version '9.0.6'
+   id 'com.dorongold.task-tree' version '2.1.0' // only needed to display task dependency tree with  gradle task1 [task2 ...] taskTree
+   id 'com.palantir.git-version' version '0.13.0' apply false
+@@ -183,6 +183,7 @@
+   testDir = string("${jalviewDir}/${bareTestSourceDir}")
+   classesDir = string("${jalviewDir}/${classes_dir}")
++  destinationDirectory = file(classesDir)
+   // clover
+   useClover = clover.equals("true")
+@@ -547,14 +548,14 @@
+   main {
+     java {
+       srcDirs sourceDir
+-      outputDir = file(classesDir)
++      destinationDirectory = file(classesDir)
+     }
+     resources {
+       srcDirs = [ resourcesBuildDir, docBuildDir, helpBuildDir ]
+     }
+-    compileClasspath = files(sourceSets.main.java.outputDir)
++    compileClasspath = files(sourceSets.main.java.destinationDirectory)
+     compileClasspath += fileTree(dir: "${jalviewDir}/${libDir}", include: ["*.jar"])
+     runtimeClasspath = compileClasspath
+@@ -564,14 +565,14 @@
+   clover {
+     java {
+       srcDirs cloverInstrDir
+-      outputDir = cloverClassesDir
++      destinationDirectory = cloverClassesDir
+     }
+     resources {
+       srcDirs = sourceSets.main.resources.srcDirs
+     }
+-    compileClasspath = files( sourceSets.clover.java.outputDir )
++    compileClasspath = files( sourceSets.clover.java.destinationDirectory )
+     //compileClasspath += files( testClassesDir )
+     compileClasspath += fileTree(dir: "${jalviewDir}/${libDir}", include: ["*.jar"])
+     compileClasspath += fileTree(dir: "${jalviewDir}/${clover_lib_dir}", include: ["*.jar"])
+@@ -583,14 +584,14 @@
+   test {
+     java {
+       srcDirs testSourceDir
+-      outputDir = file(testClassesDir)
++      destinationDirectory = file(testClassesDir)
+     }
+     resources {
+       srcDirs = useClover ? sourceSets.clover.resources.srcDirs : sourceSets.main.resources.srcDirs
+     }
+-    compileClasspath = files( sourceSets.test.java.outputDir )
++    compileClasspath = files( sourceSets.test.java.destinationDirectory )
+     compileClasspath += useClover ? sourceSets.clover.compileClasspath : sourceSets.main.compileClasspath
+     compileClasspath += fileTree(dir: "${jalviewDir}/${utils_dir}/testnglibs", include: ["**/*.jar"])
+@@ -615,7 +616,7 @@
+   }
+   classpath {
+-    //defaultOutputDir = sourceSets.main.java.outputDir
++    //defaultOutputDir = sourceSets.main.java.destinationDirectory
+     configurations.each{ c->
+       if (c.isCanBeResolved()) {
+         minusConfigurations += [c]
+@@ -654,7 +655,7 @@
+         HashMap<String, Boolean> alreadyAddedLibPath = new HashMap<>();
+         sourceSets.main.compileClasspath.findAll { it.name.endsWith(".jar") }.any {
+-          //don't want to add outputDir as eclipse is using its own output dir in bin/main
++          //don't want to add destinationDirectory as eclipse is using its own output dir in bin/main
+           if (it.isDirectory() || ! it.exists()) {
+             // don't add dirs to classpath, especially if they don't exist
+             return false // groovy "continue" in .any closure
+@@ -674,7 +675,7 @@
+         }
+         sourceSets.test.compileClasspath.findAll { it.name.endsWith(".jar") }.any {
+-          //no longer want to add outputDir as eclipse is using its own output dir in bin/main
++          //no longer want to add destinationDirectory as eclipse is using its own output dir in bin/main
+           if (it.isDirectory() || ! it.exists()) {
+             // don't add dirs to classpath
+             return false // groovy "continue" in .any closure
+@@ -1051,7 +1052,7 @@
+ clean {
+   doFirst {
+-    delete sourceSets.main.java.outputDir
++    delete sourceSets.main.java.destinationDirectory
+   }
+ }
+@@ -1059,7 +1060,7 @@
+ cleanTest {
+   dependsOn cleanClover
+   doFirst {
+-    delete sourceSets.test.java.outputDir
++    delete sourceSets.test.java.destinationDirectory
+   }
+ }
+@@ -1151,7 +1152,7 @@
+ task copyDocs(type: Copy) {
+   def inputDir = "${jalviewDir}/${doc_dir}"
+-  def outputDir = "${docBuildDir}/${doc_dir}"
++  def destinationDirectory = "${docBuildDir}/${doc_dir}"
+   from(inputDir) {
+     include('**/*.txt')
+     include('**/*.md')
+@@ -1172,10 +1173,10 @@
+     exclude('**/*.html')
+     exclude('**/*.xml')
+   }
+-  into outputDir
++  into destinationDirectory
+   inputs.dir(inputDir)
+-  outputs.dir(outputDir)
++  outputs.dir(destinationDirectory)
+ }
+@@ -1240,15 +1241,15 @@
+       }
+       if (inFrontMatter) {
+         def m = null
+-        if (m = line =~ /^date:\s*(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})/) {
++        if (m == line =~ /^date:\s*(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})/) {
+           map["date"] = new Date().parse("yyyy-MM-dd HH:mm:ss", m[0][1])
+-        } else if (m = line =~ /^date:\s*(\d{4}-\d{2}-\d{2})/) {
++        } else if (m == line =~ /^date:\s*(\d{4}-\d{2}-\d{2})/) {
+           map["date"] = new Date().parse("yyyy-MM-dd", m[0][1])
+-        } else if (m = line =~ /^channel:\s*(\S+)/) {
++        } else if (m == line =~ /^channel:\s*(\S+)/) {
+           map["channel"] = m[0][1]
+-        } else if (m = line =~ /^version:\s*(\S+)/) {
++        } else if (m == line =~ /^version:\s*(\S+)/) {
+           map["version"] = m[0][1]
+-        } else if (m = line =~ /^\s*([^:]+)\s*:\s*(\S.*)/) {
++        } else if (m == line =~ /^\s*([^:]+)\s*:\s*(\S.*)/) {
+           map[ m[0][1] ] = m[0][2]
+         }
+         if (dateOnly && map["date"] != null) {
+@@ -1307,7 +1308,7 @@
+       def inSection = false
+       changes.eachLine { line ->
+         def m = null
+-        if (m = line =~ /^##([^#].*)$/) {
++        if (m == line =~ /^##([^#].*)$/) {
+           if (inSection) {
+             changesHugo += "</div>\n\n"
+           }
+@@ -1317,7 +1318,7 @@
+           section = section.replaceAll(/[^a-z0-9_\-]/, "")
+           changesHugo += "<div class=\"${section}\">\n\n"
+           inSection = true
+-        } else if (m = line =~ /^(\s*-\s*)<!--([^>]+)-->(.*?)(<br\/?>)?\s*$/) {
++        } else if (m == line =~ /^(\s*-\s*)<!--([^>]+)-->(.*?)(<br\/?>)?\s*$/) {
+           def comment = m[0][2].trim()
+           if (comment != "") {
+             comment = comment.replaceAll('"', "&quot;")
+@@ -1387,7 +1388,7 @@
+   def sectionName = null
+   content.eachLine { line ->
+     def m = null
+-    if (m = line =~ /^##([^#].*)$/) {
++    if (m == line =~ /^##([^#].*)$/) {
+       if (sectionName != null) {
+         sections[sectionName] = sectionContent
+         sectionName = null
+@@ -1410,7 +1411,7 @@
+ task copyHelp(type: Copy) {
+   def inputDir = helpSourceDir
+-  def outputDir = "${helpBuildDir}/${help_dir}"
++  def destinationDirectory = "${helpBuildDir}/${help_dir}"
+   from(inputDir) {
+     include('**/*.txt')
+     include('**/*.md')
+@@ -1435,14 +1436,15 @@
+     exclude('**/*.xml')
+     exclude('**/*.jhm')
+   }
+-  into outputDir
++  into destinationDirectory
+   inputs.dir(inputDir)
+   outputs.files(helpFile)
+-  outputs.dir(outputDir)
++  outputs.dir(destinationDirectory)
+ }
++/*
+ task releasesTemplates {
+   group "help"
+   description "Recreate whatsNew.html and releases.html from markdown files and templates in help"
+@@ -1519,9 +1521,9 @@
+       def lm = null
+       def rContentProcessed = ""
+       rContent.eachLine { line ->
+-        if (lm = line =~ /^(\s*-)(\s*<!--[^>]*?-->)(.*)$/) {
++        if (lm == line =~ /^(\s*-)(\s*<!--[^>]*?-->)(.*)$/) {
+           line = "${lm[0][1]}${lm[0][3]}${lm[0][2]}"
+-      } else if (lm = line =~ /^###([^#]+.*)$/) {
++      } else if (lm == line =~ /^###([^#]+.*)$/) {
+           line = "_${lm[0][1].trim()}_"
+         }
+         rContentProcessed += line + "\n"
+@@ -1579,13 +1581,14 @@
+   outputs.file(whatsnewHtmlFile)
+ }
++*/
+ task copyResources(type: Copy) {
+   group = "build"
+   description = "Copy (and make text substitutions in) the resources dir to the build area"
+   def inputDir = resourceDir
+-  def outputDir = resourcesBuildDir
++  def destinationDirectory = resourcesBuildDir
+   from(inputDir) {
+     include('**/*.txt')
+     include('**/*.md')
+@@ -1606,10 +1609,10 @@
+     exclude('**/*.html')
+     exclude('**/*.xml')
+   }
+-  into outputDir
++  into destinationDirectory
+   inputs.dir(inputDir)
+-  outputs.dir(outputDir)
++  outputs.dir(destinationDirectory)
+ }
+ task copyChannelResources(type: Copy) {
+@@ -1618,16 +1621,17 @@
+   description = "Copy the channel resources dir to the build resources area"
+   def inputDir = "${channelDir}/${resource_dir}"
+-  def outputDir = resourcesBuildDir
++  def destinationDirectory = resourcesBuildDir
+   from inputDir
+-  into outputDir
++  into destinationDirectory
+   inputs.dir(inputDir)
+-  outputs.dir(outputDir)
++  outputs.dir(destinationDirectory)
+ }
+ task createBuildProperties(type: WriteProperties) {
+   dependsOn copyResources
++  dependsOn copyChannelResources
+   group = "build"
+   description = "Create the ${buildProperties} file"
+   
+@@ -1651,6 +1655,7 @@
+ task buildIndices(type: JavaExec) {
+   dependsOn copyHelp
++  //dependsOn releasesTemplates
+   classpath = sourceSets.main.compileClasspath
+   main = "com.sun.java.help.search.Indexer"
+   workingDir = "${helpBuildDir}/${help_dir}"
+@@ -1678,15 +1683,25 @@
+   dependsOn buildResources
+   dependsOn copyDocs
+   dependsOn copyHelp
+-  dependsOn releasesTemplates
++  //dependsOn releasesTemplates
+   dependsOn convertMdFiles
+   dependsOn buildIndices
+ }
++// random block of dependencies
+ compileJava.dependsOn prepare
+ run.dependsOn compileJava
+ //run.dependsOn prepare
++compileTestJava.dependsOn compileJava //
++compileTestJava.dependsOn buildIndices //
++processResources.dependsOn copyChannelResources //
++processResources.dependsOn copyResources //
++processResources.dependsOn createBuildProperties //
++processResources.dependsOn copyDocs //
++processResources.dependsOn convertMdFiles //
++processResources.dependsOn copyHelp //
++processResources.dependsOn buildIndices //
+ //testReportDirName = "test-reports" // note that test workingDir will be $jalviewDir
+@@ -1731,6 +1746,7 @@
+ }
++/*
+ task compileLinkCheck(type: JavaCompile) {
+   options.fork = true
+   classpath = files("${jalviewDir}/${utils_dir}")
+@@ -1765,6 +1781,7 @@
+   inputs.dir(helpBuildDir)
+   outputs.file(helpLinksCheckerOutFile)
+ }
++*/
+ // import the pubhtmlhelp target
+@@ -1779,10 +1796,14 @@
+   }
+ }
++// block of dependencies
++//compileTestJava.dependsOn compileLinkCheck //
++//copyChannelResources.dependsOn compileLinkCheck //
++//convertMdFiles.dependsOn compileLinkCheck //
+ jar {
+   dependsOn prepare
+-  dependsOn linkCheck
++  dependsOn //linkCheck
+   manifest {
+     attributes "Main-Class": main_class,
+@@ -1792,8 +1813,8 @@
+     "Implementation-Version": JALVIEW_VERSION
+   }
+-  def outputDir = "${jalviewDir}/${package_dir}"
+-  destinationDirectory = file(outputDir)
++  def destinationDirectory = "${jalviewDir}/${package_dir}"
++  destinationDirectory = file(destinationDirectory)
+   archiveFileName = rootProject.name+".jar"
+   duplicatesStrategy "EXCLUDE"
+@@ -1804,11 +1825,11 @@
+   exclude "**/*.jar"
+   exclude "**/*.jar.*"
+-  inputs.dir(sourceSets.main.java.outputDir)
++  inputs.dir(sourceSets.main.java.destinationDirectory)
+   sourceSets.main.resources.srcDirs.each{ dir ->
+     inputs.dir(dir)
+   }
+-  outputs.file("${outputDir}/${archiveFileName}")
++  outputs.file("${destinationDirectory}/${archiveFileName}")
+ }
+@@ -1867,7 +1888,7 @@
+   mainClassName = shadow_jar_main_class
+   mergeServiceFiles()
+-  classifier = "all-"+JALVIEW_VERSION+"-j"+JAVA_VERSION
++  archiveClassifier = "all-"+JALVIEW_VERSION+"-j"+JAVA_VERSION
+   minimize()
+ }
+@@ -2922,10 +2943,10 @@
+   def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjs_eclipse_dropins_dir}", include: "*.jar")
+   inputFiles += file("${jalviewDir}/${jalviewjsJ2sPlugin}")
+-  def outputDir = "${jalviewDir}/${jalviewjsBuildDir}/${jalviewjs_eclipse_tmp_dropins_dir}"
++  def destinationDirectory = "${jalviewDir}/${jalviewjsBuildDir}/${jalviewjs_eclipse_tmp_dropins_dir}"
+   from inputFiles
+-  into outputDir
++  into destinationDirectory
+ }
+@@ -3033,13 +3054,13 @@
+   dependsOn jalviewjsTransferUnzipAllLibs
+   def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjsTransferSiteLibDir}")
+   inputFiles += fileTree(dir: "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}")
+-  def outputDir = "${jalviewDir}/${jalviewjsSiteDir}"
++  def destinationDirectory = "${jalviewDir}/${jalviewjsSiteDir}"
+   from inputFiles
+-  into outputDir
++  into destinationDirectory
+   def outputFiles = []
+   rename { filename ->
+-    outputFiles += "${outputDir}/${filename}"
++    outputFiles += "${destinationDirectory}/${filename}"
+     null
+   }
+   preserve {
+@@ -3058,13 +3079,13 @@
+   dependsOn buildResources
+   def inputFiles = fileTree(dir: resourcesBuildDir)
+-  def outputDir = "${jalviewDir}/${jalviewjsSiteDir}/${jalviewjs_j2s_subdir}"
++  def destinationDirectory = "${jalviewDir}/${jalviewjsSiteDir}/${jalviewjs_j2s_subdir}"
+   from inputFiles
+-  into outputDir
++  into destinationDirectory
+   def outputFiles = []
+   rename { filename ->
+-    outputFiles += "${outputDir}/${filename}"
++    outputFiles += "${destinationDirectory}/${filename}"
+     null
+   }
+   preserve {
+@@ -3077,13 +3098,13 @@
+ task jalviewjsSyncSiteResources (type: Sync) {
+   def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjs_site_resource_dir}")
+-  def outputDir = "${jalviewDir}/${jalviewjsSiteDir}"
++  def destinationDirectory = "${jalviewDir}/${jalviewjsSiteDir}"
+   from inputFiles
+-  into outputDir
++  into destinationDirectory
+   def outputFiles = []
+   rename { filename ->
+-    outputFiles += "${outputDir}/${filename}"
++    outputFiles += "${destinationDirectory}/${filename}"
+     null
+   }
+   preserve {
+@@ -3097,13 +3118,13 @@
+ task jalviewjsSyncBuildProperties (type: Sync) {
+   dependsOn createBuildProperties
+   def inputFiles = [file(buildProperties)]
+-  def outputDir = "${jalviewDir}/${jalviewjsSiteDir}/${jalviewjs_j2s_subdir}"
++  def destinationDirectory = "${jalviewDir}/${jalviewjsSiteDir}/${jalviewjs_j2s_subdir}"
+   from inputFiles
+-  into outputDir
++  into destinationDirectory
+   def outputFiles = []
+   rename { filename ->
+-    outputFiles += "${outputDir}/${filename}"
++    outputFiles += "${destinationDirectory}/${filename}"
+     null
+   }
+   preserve {
+@@ -3322,7 +3343,7 @@
+   def swingJ2sDir = "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}/${jalviewjs_j2s_subdir}"
+   def libJ2sDir = "${jalviewDir}/${jalviewjsTransferSiteLibDir}/${jalviewjs_j2s_subdir}"
+   def jsDir = "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}/${jalviewjs_js_subdir}"
+-  def outputDir = "${jalviewDir}/${jalviewjsTransferSiteCoreDir}/${jalviewjs_j2s_subdir}/core"
++  def destinationDirectory = "${jalviewDir}/${jalviewjsTransferSiteCoreDir}/${jalviewjs_j2s_subdir}/core"
+   def prefixFile = "${jsDir}/core/coretop2.js"
+   def suffixFile = "${jsDir}/core/corebottom2.js"
+@@ -3366,8 +3387,8 @@
+     }
+     def list = fileTree(dir: j2sDir, includes: filelist)
+-    def jsfile = "${outputDir}/core${name}.js"
+-    def zjsfile = "${outputDir}/core${name}.z.js"
++    def jsfile = "${destinationDirectory}/core${name}.js"
++    def zjsfile = "${destinationDirectory}/core${name}.z.js"
+     jalviewjsCoreClasslists += [
+       'jsfile': jsfile,
+@@ -3385,8 +3406,8 @@
+   // _stevesoft core. add any cores without a classlist here (and the inputs and outputs)
+   def stevesoftClasslistName = "_stevesoft"
+   def stevesoftClasslist = [
+-    'jsfile': "${outputDir}/core${stevesoftClasslistName}.js",
+-    'zjsfile': "${outputDir}/core${stevesoftClasslistName}.z.js",
++    'jsfile': "${destinationDirectory}/core${stevesoftClasslistName}.js",
++    'zjsfile': "${destinationDirectory}/core${stevesoftClasslistName}.z.js",
+     'list': fileTree(dir: j2sDir, include: "com/stevesoft/pat/**/*.js"),
+     'name': stevesoftClasslistName
+   ]
+@@ -3419,8 +3440,8 @@
+     ]
+   )
+   def allClasslist = [
+-    'jsfile': "${outputDir}/core${allClasslistName}.js",
+-    'zjsfile': "${outputDir}/core${allClasslistName}.z.js",
++    'jsfile': "${destinationDirectory}/core${allClasslistName}.js",
++    'zjsfile': "${destinationDirectory}/core${allClasslistName}.z.js",
+     'list': allJsFiles,
+     'name': allClasslistName
+   ]
+@@ -3473,11 +3494,11 @@
+   dependsOn jalviewjsBuildAllCores
+   def inputFileName = "${jalviewDir}/${j2s_coretemplate_html}"
+   def inputFile = file(inputFileName)
+-  def outputDir = "${jalviewDir}/${jalviewjsTransferSiteCoreDir}"
++  def destinationDirectory = "${jalviewDir}/${jalviewjsTransferSiteCoreDir}"
+   def outputFiles = []
+   jalviewjsCoreClasslists.each { cl ->
+-    def outputFile = "${outputDir}/${jalviewjsJalviewTemplateName}_${cl.name}.html"
++    def outputFile = "${destinationDirectory}/${jalviewjsJalviewTemplateName}_${cl.name}.html"
+     cl['outputfile'] = outputFile
+     outputFiles += outputFile
+   }
+@@ -3496,13 +3517,13 @@
+   dependsOn jalviewjsBuildAllCores
+   dependsOn jalviewjsPublishCoreTemplates
+   def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjsTransferSiteCoreDir}")
+-  def outputDir = "${jalviewDir}/${jalviewjsSiteDir}"
++  def destinationDirectory = "${jalviewDir}/${jalviewjsSiteDir}"
+   from inputFiles
+-  into outputDir
++  into destinationDirectory
+   def outputFiles = []
+   rename { filename ->
+-    outputFiles += "${outputDir}/${filename}"
++    outputFiles += "${destinationDirectory}/${filename}"
+     null
+   }
+   preserve {
index 6df3ec9..90686b3 100644 (file)
@@ -193,6 +193,7 @@ ext {
   testDir = string("${jalviewDir}/${bareTestSourceDir}")
 
   classesDir = string("${jalviewDir}/${classes_dir}")
+  destinationDirectory = file(classesDir)
 
   // clover
   useClover = clover.equals("true")
@@ -590,14 +591,14 @@ sourceSets {
   main {
     java {
       srcDirs sourceDir
-      outputDir = file(classesDir)
+      destinationDirectory = file(classesDir)
     }
 
     resources {
       srcDirs = [ resourcesBuildDir, docBuildDir, helpBuildDir ]
     }
 
-    compileClasspath = files(sourceSets.main.java.outputDir)
+    compileClasspath = files(sourceSets.main.java.destinationDirectory)
     compileClasspath += fileTree(dir: "${jalviewDir}/${libDir}", include: ["*.jar"])
 
     runtimeClasspath = compileClasspath
@@ -607,14 +608,14 @@ sourceSets {
   clover {
     java {
       srcDirs cloverInstrDir
-      outputDir = cloverClassesDir
+      destinationDirectory = cloverClassesDir
     }
 
     resources {
       srcDirs = sourceSets.main.resources.srcDirs
     }
 
-    compileClasspath = files( sourceSets.clover.java.outputDir )
+    compileClasspath = files( sourceSets.clover.java.destinationDirectory )
     //compileClasspath += files( testClassesDir )
     compileClasspath += fileTree(dir: "${jalviewDir}/${libDir}", include: ["*.jar"])
     compileClasspath += fileTree(dir: "${jalviewDir}/${clover_lib_dir}", include: ["*.jar"])
@@ -626,14 +627,14 @@ sourceSets {
   test {
     java {
       srcDirs testSourceDir
-      outputDir = file(testClassesDir)
+      destinationDirectory = file(testClassesDir)
     }
 
     resources {
       srcDirs = useClover ? sourceSets.clover.resources.srcDirs : sourceSets.main.resources.srcDirs
     }
 
-    compileClasspath = files( sourceSets.test.java.outputDir )
+    compileClasspath = files( sourceSets.test.java.destinationDirectory )
     compileClasspath += useClover ? sourceSets.clover.compileClasspath : sourceSets.main.compileClasspath
     compileClasspath += fileTree(dir: "${jalviewDir}/${utils_dir}/testnglibs", include: ["**/*.jar"])
 
@@ -658,7 +659,7 @@ eclipse {
   }
 
   classpath {
-    //defaultOutputDir = sourceSets.main.java.outputDir
+    //defaultOutputDir = sourceSets.main.java.destinationDirectory
     configurations.each{ c->
       if (c.isCanBeResolved()) {
         minusConfigurations += [c]
@@ -697,7 +698,7 @@ eclipse {
         HashMap<String, Boolean> alreadyAddedLibPath = new HashMap<>();
 
         sourceSets.main.compileClasspath.findAll { it.name.endsWith(".jar") }.any {
-          //don't want to add outputDir as eclipse is using its own output dir in bin/main
+          //don't want to add destinationDirectory as eclipse is using its own output dir in bin/main
           if (it.isDirectory() || ! it.exists()) {
             // don't add dirs to classpath, especially if they don't exist
             return false // groovy "continue" in .any closure
@@ -717,7 +718,7 @@ eclipse {
         }
 
         sourceSets.test.compileClasspath.findAll { it.name.endsWith(".jar") }.any {
-          //no longer want to add outputDir as eclipse is using its own output dir in bin/main
+          //no longer want to add destinationDirectory as eclipse is using its own output dir in bin/main
           if (it.isDirectory() || ! it.exists()) {
             // don't add dirs to classpath
             return false // groovy "continue" in .any closure
@@ -1094,7 +1095,7 @@ compileTestJava {
 
 clean {
   doFirst {
-    delete sourceSets.main.java.outputDir
+    delete sourceSets.main.java.destinationDirectory
   }
 }
 
@@ -1102,7 +1103,7 @@ clean {
 cleanTest {
   dependsOn cleanClover
   doFirst {
-    delete sourceSets.test.java.outputDir
+    delete sourceSets.test.java.destinationDirectory
   }
 }
 
@@ -1194,7 +1195,7 @@ def convertMdToHtml (FileTree mdFiles, File cssFile) {
 
 task copyDocs(type: Copy) {
   def inputDir = "${jalviewDir}/${doc_dir}"
-  def outputDir = "${docBuildDir}/${doc_dir}"
+  def destinationDirectory = "${docBuildDir}/${doc_dir}"
   from(inputDir) {
     include('**/*.txt')
     include('**/*.md')
@@ -1215,10 +1216,10 @@ task copyDocs(type: Copy) {
     exclude('**/*.html')
     exclude('**/*.xml')
   }
-  into outputDir
+  into destinationDirectory
 
   inputs.dir(inputDir)
-  outputs.dir(outputDir)
+  outputs.dir(destinationDirectory)
 }
 
 
@@ -1283,15 +1284,15 @@ def mdFileComponents(File mdFile, def dateOnly=false) {
       }
       if (inFrontMatter) {
         def m = null
-        if (m = line =~ /^date:\s*(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})/) {
+        if (m == line =~ /^date:\s*(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})/) {
           map["date"] = new Date().parse("yyyy-MM-dd HH:mm:ss", m[0][1])
-        } else if (m = line =~ /^date:\s*(\d{4}-\d{2}-\d{2})/) {
+        } else if (m == line =~ /^date:\s*(\d{4}-\d{2}-\d{2})/) {
           map["date"] = new Date().parse("yyyy-MM-dd", m[0][1])
-        } else if (m = line =~ /^channel:\s*(\S+)/) {
+        } else if (m == line =~ /^channel:\s*(\S+)/) {
           map["channel"] = m[0][1]
-        } else if (m = line =~ /^version:\s*(\S+)/) {
+        } else if (m == line =~ /^version:\s*(\S+)/) {
           map["version"] = m[0][1]
-        } else if (m = line =~ /^\s*([^:]+)\s*:\s*(\S.*)/) {
+        } else if (m == line =~ /^\s*([^:]+)\s*:\s*(\S.*)/) {
           map[ m[0][1] ] = m[0][2]
         }
         if (dateOnly && map["date"] != null) {
@@ -1350,7 +1351,7 @@ task hugoTemplates {
       def inSection = false
       changes.eachLine { line ->
         def m = null
-        if (m = line =~ /^##([^#].*)$/) {
+        if (m == line =~ /^##([^#].*)$/) {
           if (inSection) {
             changesHugo += "</div>\n\n"
           }
@@ -1360,7 +1361,7 @@ task hugoTemplates {
           section = section.replaceAll(/[^a-z0-9_\-]/, "")
           changesHugo += "<div class=\"${section}\">\n\n"
           inSection = true
-        } else if (m = line =~ /^(\s*-\s*)<!--([^>]+)-->(.*?)(<br\/?>)?\s*$/) {
+        } else if (m == line =~ /^(\s*-\s*)<!--([^>]+)-->(.*?)(<br\/?>)?\s*$/) {
           def comment = m[0][2].trim()
           if (comment != "") {
             comment = comment.replaceAll('"', "&quot;")
@@ -1430,7 +1431,7 @@ def getMdSections(String content) {
   def sectionName = null
   content.eachLine { line ->
     def m = null
-    if (m = line =~ /^##([^#].*)$/) {
+    if (m == line =~ /^##([^#].*)$/) {
       if (sectionName != null) {
         sections[sectionName] = sectionContent
         sectionName = null
@@ -1453,7 +1454,7 @@ def getMdSections(String content) {
 
 task copyHelp(type: Copy) {
   def inputDir = helpSourceDir
-  def outputDir = "${helpBuildDir}/${help_dir}"
+  def destinationDirectory = "${helpBuildDir}/${help_dir}"
   from(inputDir) {
     include('**/*.txt')
     include('**/*.md')
@@ -1478,14 +1479,15 @@ task copyHelp(type: Copy) {
     exclude('**/*.xml')
     exclude('**/*.jhm')
   }
-  into outputDir
+  into destinationDirectory
 
   inputs.dir(inputDir)
   outputs.files(helpFile)
-  outputs.dir(outputDir)
+  outputs.dir(destinationDirectory)
 }
 
 
+/*
 task releasesTemplates {
   group "help"
   description "Recreate whatsNew.html and releases.html from markdown files and templates in help"
@@ -1562,9 +1564,9 @@ task releasesTemplates {
       def lm = null
       def rContentProcessed = ""
       rContent.eachLine { line ->
-        if (lm = line =~ /^(\s*-)(\s*<!--[^>]*?-->)(.*)$/) {
+        if (lm == line =~ /^(\s*-)(\s*<!--[^>]*?-->)(.*)$/) {
           line = "${lm[0][1]}${lm[0][3]}${lm[0][2]}"
-      } else if (lm = line =~ /^###([^#]+.*)$/) {
+      } else if (lm == line =~ /^###([^#]+.*)$/) {
           line = "_${lm[0][1].trim()}_"
         }
         rContentProcessed += line + "\n"
@@ -1622,13 +1624,14 @@ task releasesTemplates {
   outputs.file(whatsnewHtmlFile)
 }
 
+*/
 
 task copyResources(type: Copy) {
   group = "build"
   description = "Copy (and make text substitutions in) the resources dir to the build area"
 
   def inputDir = resourceDir
-  def outputDir = resourcesBuildDir
+  def destinationDirectory = resourcesBuildDir
   from(inputDir) {
     include('**/*.txt')
     include('**/*.md')
@@ -1649,10 +1652,10 @@ task copyResources(type: Copy) {
     exclude('**/*.html')
     exclude('**/*.xml')
   }
-  into outputDir
+  into destinationDirectory
 
   inputs.dir(inputDir)
-  outputs.dir(outputDir)
+  outputs.dir(destinationDirectory)
 }
 
 task copyChannelResources(type: Copy) {
@@ -1661,7 +1664,7 @@ task copyChannelResources(type: Copy) {
   description = "Copy the channel resources dir to the build resources area"
 
   def inputDir = "${channelDir}/${resource_dir}"
-  def outputDir = resourcesBuildDir
+  def destinationDirectory = resourcesBuildDir
   from(inputDir) {
     include(channel_props)
     filter(ReplaceTokens,
@@ -1675,14 +1678,15 @@ task copyChannelResources(type: Copy) {
   from(inputDir) {
     exclude(channel_props)
   }
-  into outputDir
+  into destinationDirectory
 
   inputs.dir(inputDir)
-  outputs.dir(outputDir)
+  outputs.dir(destinationDirectory)
 }
 
 task createBuildProperties(type: WriteProperties) {
   dependsOn copyResources
+  dependsOn copyChannelResources
   group = "build"
   description = "Create the ${buildProperties} file"
   
@@ -1706,6 +1710,7 @@ task createBuildProperties(type: WriteProperties) {
 
 task buildIndices(type: JavaExec) {
   dependsOn copyHelp
+  //dependsOn releasesTemplates
   classpath = sourceSets.main.compileClasspath
   main = "com.sun.java.help.search.Indexer"
   workingDir = "${helpBuildDir}/${help_dir}"
@@ -1733,18 +1738,25 @@ task prepare {
   dependsOn buildResources
   dependsOn copyDocs
   dependsOn copyHelp
-  dependsOn releasesTemplates
+  //dependsOn releasesTemplates
   dependsOn convertMdFiles
   dependsOn buildIndices
 }
 
 
+// random block of dependencies
 compileJava.dependsOn prepare
 run.dependsOn compileJava
-compileTestJava.dependsOn compileJava
-
-
-
+//run.dependsOn prepare
+compileTestJava.dependsOn compileJava //
+compileTestJava.dependsOn buildIndices //
+processResources.dependsOn copyChannelResources //
+processResources.dependsOn copyResources //
+processResources.dependsOn createBuildProperties //
+processResources.dependsOn copyDocs //
+processResources.dependsOn convertMdFiles //
+processResources.dependsOn copyHelp //
+processResources.dependsOn buildIndices //
 test {
   group = "Verification"
   description = "Runs all testTaskN tasks)"
@@ -2037,6 +2049,7 @@ private static void printResults(allResults) {
 /* END of test tasks results summary */
 
 
+/*
 task compileLinkCheck(type: JavaCompile) {
   options.fork = true
   classpath = files("${jalviewDir}/${utils_dir}")
@@ -2071,6 +2084,7 @@ task linkCheck(type: JavaExec) {
   inputs.dir(helpBuildDir)
   outputs.file(helpLinksCheckerOutFile)
 }
+*/
 
 
 // import the pubhtmlhelp target
@@ -2085,10 +2099,14 @@ task cleanPackageDir(type: Delete) {
   }
 }
 
+// block of dependencies
+//compileTestJava.dependsOn compileLinkCheck //
+//copyChannelResources.dependsOn compileLinkCheck //
+//convertMdFiles.dependsOn compileLinkCheck //
 
 jar {
   dependsOn prepare
-  dependsOn linkCheck
+  dependsOn //linkCheck
 
   manifest {
     attributes "Main-Class": main_class,
@@ -2098,8 +2116,8 @@ jar {
     "Implementation-Version": JALVIEW_VERSION
   }
 
-  def outputDir = "${jalviewDir}/${package_dir}"
-  destinationDirectory = file(outputDir)
+  def destinationDirectory = "${jalviewDir}/${package_dir}"
+  destinationDirectory = file(destinationDirectory)
   archiveFileName = rootProject.name+".jar"
   duplicatesStrategy "EXCLUDE"
 
@@ -2110,11 +2128,11 @@ jar {
   exclude "**/*.jar"
   exclude "**/*.jar.*"
 
-  inputs.dir(sourceSets.main.java.outputDir)
+  inputs.dir(sourceSets.main.java.destinationDirectory)
   sourceSets.main.resources.srcDirs.each{ dir ->
     inputs.dir(dir)
   }
-  outputs.file("${outputDir}/${archiveFileName}")
+  outputs.file("${destinationDirectory}/${archiveFileName}")
 }
 
 
@@ -2207,7 +2225,7 @@ shadowJar {
   // this mainClassName is mandatory but gets ignored due to manifest created in doFirst{}. Set the Main-Class as an attribute in launcherJar instead
   mainClassName = shadow_jar_main_class
   mergeServiceFiles()
-  classifier = "all-"+JALVIEW_VERSION+"-j"+JAVA_VERSION
+  archiveClassifier = "all-"+JALVIEW_VERSION+"-j"+JAVA_VERSION
   minimize()
 }
 
@@ -3388,10 +3406,10 @@ task jalviewjsEclipseCopyDropins(type: Copy) {
 
   def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjs_eclipse_dropins_dir}", include: "*.jar")
   inputFiles += file("${jalviewDir}/${jalviewjsJ2sPlugin}")
-  def outputDir = "${jalviewDir}/${jalviewjsBuildDir}/${jalviewjs_eclipse_tmp_dropins_dir}"
+  def destinationDirectory = "${jalviewDir}/${jalviewjsBuildDir}/${jalviewjs_eclipse_tmp_dropins_dir}"
 
   from inputFiles
-  into outputDir
+  into destinationDirectory
 }
 
 
@@ -3499,13 +3517,13 @@ task jalviewjsSyncAllLibs (type: Sync) {
   dependsOn jalviewjsTransferUnzipAllLibs
   def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjsTransferSiteLibDir}")
   inputFiles += fileTree(dir: "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}")
-  def outputDir = "${jalviewDir}/${jalviewjsSiteDir}"
+  def destinationDirectory = "${jalviewDir}/${jalviewjsSiteDir}"
 
   from inputFiles
-  into outputDir
+  into destinationDirectory
   def outputFiles = []
   rename { filename ->
-    outputFiles += "${outputDir}/${filename}"
+    outputFiles += "${destinationDirectory}/${filename}"
     null
   }
   preserve {
@@ -3524,13 +3542,13 @@ task jalviewjsSyncResources (type: Sync) {
   dependsOn buildResources
 
   def inputFiles = fileTree(dir: resourcesBuildDir)
-  def outputDir = "${jalviewDir}/${jalviewjsSiteDir}/${jalviewjs_j2s_subdir}"
+  def destinationDirectory = "${jalviewDir}/${jalviewjsSiteDir}/${jalviewjs_j2s_subdir}"
 
   from inputFiles
-  into outputDir
+  into destinationDirectory
   def outputFiles = []
   rename { filename ->
-    outputFiles += "${outputDir}/${filename}"
+    outputFiles += "${destinationDirectory}/${filename}"
     null
   }
   preserve {
@@ -3543,13 +3561,13 @@ task jalviewjsSyncResources (type: Sync) {
 
 task jalviewjsSyncSiteResources (type: Sync) {
   def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjs_site_resource_dir}")
-  def outputDir = "${jalviewDir}/${jalviewjsSiteDir}"
+  def destinationDirectory = "${jalviewDir}/${jalviewjsSiteDir}"
 
   from inputFiles
-  into outputDir
+  into destinationDirectory
   def outputFiles = []
   rename { filename ->
-    outputFiles += "${outputDir}/${filename}"
+    outputFiles += "${destinationDirectory}/${filename}"
     null
   }
   preserve {
@@ -3563,13 +3581,13 @@ task jalviewjsSyncSiteResources (type: Sync) {
 task jalviewjsSyncBuildProperties (type: Sync) {
   dependsOn createBuildProperties
   def inputFiles = [file(buildProperties)]
-  def outputDir = "${jalviewDir}/${jalviewjsSiteDir}/${jalviewjs_j2s_subdir}"
+  def destinationDirectory = "${jalviewDir}/${jalviewjsSiteDir}/${jalviewjs_j2s_subdir}"
 
   from inputFiles
-  into outputDir
+  into destinationDirectory
   def outputFiles = []
   rename { filename ->
-    outputFiles += "${outputDir}/${filename}"
+    outputFiles += "${destinationDirectory}/${filename}"
     null
   }
   preserve {
@@ -3788,7 +3806,7 @@ task jalviewjsBuildAllCores {
   def swingJ2sDir = "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}/${jalviewjs_j2s_subdir}"
   def libJ2sDir = "${jalviewDir}/${jalviewjsTransferSiteLibDir}/${jalviewjs_j2s_subdir}"
   def jsDir = "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}/${jalviewjs_js_subdir}"
-  def outputDir = "${jalviewDir}/${jalviewjsTransferSiteCoreDir}/${jalviewjs_j2s_subdir}/core"
+  def destinationDirectory = "${jalviewDir}/${jalviewjsTransferSiteCoreDir}/${jalviewjs_j2s_subdir}/core"
   def prefixFile = "${jsDir}/core/coretop2.js"
   def suffixFile = "${jsDir}/core/corebottom2.js"
 
@@ -3832,8 +3850,8 @@ task jalviewjsBuildAllCores {
     }
     def list = fileTree(dir: j2sDir, includes: filelist)
 
-    def jsfile = "${outputDir}/core${name}.js"
-    def zjsfile = "${outputDir}/core${name}.z.js"
+    def jsfile = "${destinationDirectory}/core${name}.js"
+    def zjsfile = "${destinationDirectory}/core${name}.z.js"
 
     jalviewjsCoreClasslists += [
       'jsfile': jsfile,
@@ -3851,8 +3869,8 @@ task jalviewjsBuildAllCores {
   // _stevesoft core. add any cores without a classlist here (and the inputs and outputs)
   def stevesoftClasslistName = "_stevesoft"
   def stevesoftClasslist = [
-    'jsfile': "${outputDir}/core${stevesoftClasslistName}.js",
-    'zjsfile': "${outputDir}/core${stevesoftClasslistName}.z.js",
+    'jsfile': "${destinationDirectory}/core${stevesoftClasslistName}.js",
+    'zjsfile': "${destinationDirectory}/core${stevesoftClasslistName}.z.js",
     'list': fileTree(dir: j2sDir, include: "com/stevesoft/pat/**/*.js"),
     'name': stevesoftClasslistName
   ]
@@ -3885,8 +3903,8 @@ task jalviewjsBuildAllCores {
     ]
   )
   def allClasslist = [
-    'jsfile': "${outputDir}/core${allClasslistName}.js",
-    'zjsfile': "${outputDir}/core${allClasslistName}.z.js",
+    'jsfile': "${destinationDirectory}/core${allClasslistName}.js",
+    'zjsfile': "${destinationDirectory}/core${allClasslistName}.z.js",
     'list': allJsFiles,
     'name': allClasslistName
   ]
@@ -3939,11 +3957,11 @@ task jalviewjsPublishCoreTemplates {
   dependsOn jalviewjsBuildAllCores
   def inputFileName = "${jalviewDir}/${j2s_coretemplate_html}"
   def inputFile = file(inputFileName)
-  def outputDir = "${jalviewDir}/${jalviewjsTransferSiteCoreDir}"
+  def destinationDirectory = "${jalviewDir}/${jalviewjsTransferSiteCoreDir}"
 
   def outputFiles = []
   jalviewjsCoreClasslists.each { cl ->
-    def outputFile = "${outputDir}/${jalviewjsJalviewTemplateName}_${cl.name}.html"
+    def outputFile = "${destinationDirectory}/${jalviewjsJalviewTemplateName}_${cl.name}.html"
     cl['outputfile'] = outputFile
     outputFiles += outputFile
   }
@@ -3962,13 +3980,13 @@ task jalviewjsSyncCore (type: Sync) {
   dependsOn jalviewjsBuildAllCores
   dependsOn jalviewjsPublishCoreTemplates
   def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjsTransferSiteCoreDir}")
-  def outputDir = "${jalviewDir}/${jalviewjsSiteDir}"
+  def destinationDirectory = "${jalviewDir}/${jalviewjsSiteDir}"
 
   from inputFiles
-  into outputDir
+  into destinationDirectory
   def outputFiles = []
   rename { filename ->
-    outputFiles += "${outputDir}/${filename}"
+    outputFiles += "${destinationDirectory}/${filename}"
     null
   }
   preserve {
index c732130..dab5393 100644 (file)
@@ -72,13 +72,12 @@ channel: "release"
 - <!-- JAL-4054 --> Installers built with install4j10
 - <!-- JAL-4167 --> Create separate gradle test task for some tests
 - <!-- JAL-4212 --> Prevent gradle test on macOS continuously grabbing focus
-- <!-- JAL-4111 --> Allow gradle build to create suffixed DEVELOP-... builds with channel appbase
+- <!-- JAL-4111 --> Allow gradle build to create suffixed DEVELOP-... builds with channel 
 - <!-- JAL-4288 --> Update .jvl generation in build.gradle for jalview branch builds
 - <!-- JAL-4243 --> Jalview bio.tools description maintained under jalview's git repo and bundled with source release
 - <!-- JAL-4110 --> Output stderr and stdout when running tests via gradle
 - <!-- JAL-3599 --> New gradle task providing runtime acceptance test for JalviewJS  based on Chromium for Tests (still work in progress)
 
-
 ## Issues Resolved
 - <!-- JAL-2961 --> Jmol view not always centred on structures when multiple structures are viewed
 - <!-- JAL-3772 --> Unsaved Alignment windows close without prompting to save, individually or at application quit.
diff --git a/j11lib/commons-math3-3.6.1.jar b/j11lib/commons-math3-3.6.1.jar
new file mode 100644 (file)
index 0000000..0ff582c
Binary files /dev/null and b/j11lib/commons-math3-3.6.1.jar differ
diff --git a/j8lib/commons-math3-3.6.1.jar b/j8lib/commons-math3-3.6.1.jar
new file mode 100644 (file)
index 0000000..0ff582c
Binary files /dev/null and b/j8lib/commons-math3-3.6.1.jar differ
index 8256e06..293f612 100644 (file)
@@ -98,7 +98,7 @@ action.scale_right = Scale Right
 action.by_tree_order = By Tree Order
 action.sort = Sort
 action.calculate_tree = Calculate Tree...
-action.calculate_tree_pca = Calculate Tree or PCA...
+action.calculate_tree_pca = Calculate Tree, PCA or PaSiMap...
 action.help = Help
 action.by_annotation = By Annotation...
 action.invert_sequence_selection = Invert Sequence Selection
@@ -187,6 +187,7 @@ label.amend = Amend
 label.undo_command = Undo {0}
 label.redo_command = Redo {0}
 label.principal_component_analysis = Principal Component Analysis
+label.pasimap = PaSiMap
 label.average_distance_identity = Average Distance Using % Identity
 label.neighbour_joining_identity = Neighbour Joining Using % Identity
 label.choose_calculation = Choose Calculation
@@ -319,6 +320,8 @@ label.labels = Labels
 label.output_values = Output Values...
 label.output_points = Output points...
 label.output_transformed_points = Output transformed points
+label.output_alignment = Output pairwise alignments
+label.pairwise_alignment_for_params = Pairwise alignments for {0}
 label.input_data = Input Data...
 label.nucleotide_matrix = Nucleotide matrix
 label.protein_matrix = Protein matrix
@@ -465,6 +468,7 @@ label.selection_output_command = Selection output - {0}
 label.annotation_for_displayid = <p><h2>Annotation for {0} </h2></p><p>
 label.pdb_sequence_mapping = PDB - Sequence Mapping
 label.pca_details = PCA details
+label.pasimap_details = PaSiMap details
 label.redundancy_threshold_selection = Redundancy threshold selection
 label.user_defined_colours = User defined colours
 label.jalviewLite_release = JalviewLite - Release {0}
@@ -1030,6 +1034,8 @@ label.add_new_sbrs_service = Add a new Simple Bioinformatics Rest Service
 label.edit_sbrs_entry = Edit Simple Bioinformatics Rest Service entry
 label.pca_recalculating = Recalculating PCA
 label.pca_calculating = Calculating PCA
+label.pasimap_recalculating = Recalculating PaSiMap
+label.pasimap_calculating = Calculating PaSiMap
 label.select_foreground_colour = Choose foreground colour
 label.select_colour_for_text = Select Colour for Text
 label.adjust_foreground_text_colour_threshold = Adjust Foreground Text Colour Threshold
@@ -1358,6 +1364,7 @@ label.annotation_description = Annotation Description
 label.edit_annotation_name_description = Edit Annotation Name/Description
 label.alignment = alignment
 label.pca = PCA
+label.pasimap = PaSiMap
 label.create_image_of = Create {0} image of {1}
 label.click_to_edit = Click to edit, right-click for menu
 label.backupfiles_confirm_delete = Confirm delete
index a0b9292..4936d33 100644 (file)
@@ -94,7 +94,7 @@ action.scale_right = Escala derecha
 action.by_tree_order = Por orden del Ã¡rbol
 action.sort = Ordenar
 action.calculate_tree = Calcular Ã¡rbol...
-action.calculate_tree_pca = Calcular Ã¡rbol o ACP...
+action.calculate_tree_pca = Calcular Ã¡rbol, ACP o PaSiMap...
 action.help = Ayuda
 action.by_annotation = Por anotación...
 action.invert_sequence_selection = Invertir selección de secuencias
@@ -177,6 +177,7 @@ label.amend = Modificar
 label.undo_command = Deshacer {0}
 label.redo_command = Rehacer {0}
 label.principal_component_analysis = Análisis del Componente Principal
+label.pasimap = PaSiMap
 label.average_distance_identity = Distancia Media Usando % de Identidad
 label.neighbour_joining_identity = Unir vecinos utilizando % de Identidad
 label.choose_calculation = Elegir el cálculo
@@ -277,6 +278,8 @@ label.labels = Etiquetas
 label.output_values = Valores de salida...
 label.output_points = Puntos de salida...
 label.output_transformed_points = Puntos de salida transformados
+label.output_alignment = Alineaciones de pares de salida
+label.pairwise_alignment_for_params = Alineaciiones por pares para {0}
 label.input_data = Datos de entrada...
 label.nucleotide_matrix = Matriz nucleotídica
 label.protein_matrix = Matriz proteica
@@ -938,6 +941,8 @@ label.add_new_sbrs_service = A
 label.edit_sbrs_entry = Editar entrada SBRS
 label.pca_recalculating = Recalculando ACP
 label.pca_calculating = Calculando ACP
+label.pasimap_recalculating = Recalculando PaSiMap
+label.pasimap_calculating = Calculando PaSiMap
 label.select_foreground_colour = Escoger color del primer plano
 label.select_colour_for_text = Seleccione el color del texto
 label.adjust_foreground_text_colour_threshold = Ajustar el umbral del color del texto en primer plano
@@ -1337,6 +1342,7 @@ label.annotation_description = Descripci
 label.edit_annotation_name_description = Editar el nombre/descripción de la anotación
 label.alignment = alineamiento
 label.pca = ACP
+label.pasimap = PaSiMap
 label.create_image_of = Crear imagen {0} de {1}
 label.click_to_edit = Haga clic para editar, clic en el botón derecho para ver el menú  
 label.backupfiles_confirm_delete = Confirmar borrar
index 36d2482..22cffb1 100755 (executable)
@@ -20,8 +20,6 @@
  */
 package jalview.analysis;
 
-import java.util.Locale;
-
 import jalview.analysis.scoremodels.PIDModel;
 import jalview.analysis.scoremodels.ScoreMatrix;
 import jalview.analysis.scoremodels.ScoreModels;
@@ -31,6 +29,7 @@ import jalview.datamodel.AlignmentI;
 import jalview.datamodel.Mapping;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceI;
+import jalview.math.MiscMath;
 import jalview.util.Comparison;
 import jalview.util.Format;
 import jalview.util.MapList;
@@ -39,14 +38,15 @@ import jalview.util.MessageManager;
 import java.awt.Color;
 import java.awt.Graphics;
 import java.io.PrintStream;
+import java.lang.IllegalArgumentException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.List;
 import java.util.StringTokenizer;
+import java.util.Locale;
 
 /**
- * 
- * 
  * @author $author$
  * @version $Revision$
  */
@@ -54,9 +54,13 @@ public class AlignSeq
 {
   private static final int MAX_NAME_LENGTH = 30;
 
-  private static final int GAP_OPEN_COST = 120;
+  private static final int DEFAULT_OPENCOST = 120;
+
+  private static final int DEFAULT_EXTENDCOST = 20;
+  
+  private int GAP_OPEN_COST=DEFAULT_OPENCOST;
 
-  private static final int GAP_EXTEND_COST = 20;
+  private int GAP_EXTEND_COST=DEFAULT_EXTENDCOST;
 
   private static final int GAP_INDEX = -1;
 
@@ -68,6 +72,8 @@ public class AlignSeq
 
   float[][] score;
 
+  float alignmentScore;
+
   float[][] E;
 
   float[][] F;
@@ -98,6 +104,10 @@ public class AlignSeq
 
   public String astr2 = "";
 
+  public String indelfreeAstr1 = "";
+
+  public String indelfreeAstr2 = "";
+
   /** DOCUMENT ME!! */
   public int seq1start;
 
@@ -113,6 +123,10 @@ public class AlignSeq
 
   public float maxscore;
 
+  public float meanScore;      //needed for PaSiMap
+
+  public int hypotheticMaxScore;       // needed for PaSiMap
+
   int prev = 0;
 
   StringBuffer output = new StringBuffer();
@@ -131,6 +145,11 @@ public class AlignSeq
    * @param type
    *          molecule type, either AlignSeq.PEP or AlignSeq.DNA
    */
+  public AlignSeq(int opencost, int extcost)
+  {
+    GAP_OPEN_COST = opencost;
+    GAP_EXTEND_COST = extcost;
+  }
   public AlignSeq(SequenceI s1, SequenceI s2, String type)
   {
     seqInit(s1, s1.getSequenceAsString(), s2, s2.getSequenceAsString(),
@@ -140,12 +159,12 @@ public class AlignSeq
   /**
    * Creates a new AlignSeq object.
    * 
-   * @param s1
-   *          DOCUMENT ME!
-   * @param s2
-   *          DOCUMENT ME!
+   * @param s1,string1
+   *          s1 reference sequence for string1
+   * @param s2,string2
+   *          s2 reference sequence for string2
    * @param type
-   *          DOCUMENT ME!
+   *          molecule type, either AlignSeq.PEP or AlignSeq.DNA
    */
   public AlignSeq(SequenceI s1, String string1, SequenceI s2,
           String string2, String type)
@@ -154,6 +173,23 @@ public class AlignSeq
             string2.toUpperCase(Locale.ROOT), type);
   }
 
+  public AlignSeq(SequenceI s1, SequenceI s2, String type, int opencost,
+          int extcost)
+  {
+    this(s1,s2,type);
+    GAP_OPEN_COST=opencost;
+    GAP_EXTEND_COST=extcost;
+  }
+
+  public AlignSeq(SequenceI s12, String string1, SequenceI s22,
+          String string2, String type2, int defaultOpencost,
+          int defaultExtendcost)
+  {
+    this(s12,string1,s22,string2,type2);
+    GAP_OPEN_COST=defaultOpencost;
+    GAP_EXTEND_COST=defaultExtendcost;
+  }
+
   /**
    * DOCUMENT ME!
    * 
@@ -165,6 +201,16 @@ public class AlignSeq
   }
 
   /**
+  * returns the overall score of the alignment
+  *
+  * @return
+  */
+  public float getAlignmentScore()
+  {
+    return alignmentScore;
+  }
+
+  /**
    * DOCUMENT ME!
    * 
    * @return DOCUMENT ME!
@@ -299,6 +345,13 @@ public class AlignSeq
   public void seqInit(SequenceI s1, String string1, SequenceI s2,
           String string2, String type)
   {
+    seqInit(s1,string1,s2,string2,type,GAP_OPEN_COST,GAP_EXTEND_COST);
+  }
+  public void seqInit(SequenceI s1, String string1, SequenceI s2,
+          String string2, String type, int opening,int extension)
+  {
+    GAP_OPEN_COST=opening;
+    GAP_EXTEND_COST=extension;
     this.s1 = s1;
     this.s2 = s2;
     setDefaultParams(type);
@@ -385,8 +438,6 @@ public class AlignSeq
     int trace;
     maxscore = score[i][j] / 10f;
 
-    seq1end = maxi + 1;
-    seq2end = maxj + 1;
 
     aseq1 = new int[seq1.length + seq2.length];
     aseq2 = new int[seq1.length + seq2.length];
@@ -396,6 +447,7 @@ public class AlignSeq
 
     count = (seq1.length + seq2.length) - 1;
 
+
     while (i > 0 && j > 0)
     {
       aseq1[count] = seq1[i];
@@ -441,6 +493,137 @@ public class AlignSeq
       sb2.append(s2str.charAt(j));
     }
 
+
+    /*
+     * we built the character strings backwards, so now
+     * reverse them to convert to sequence strings
+     */
+    astr1 = sb1.reverse().toString();
+    astr2 = sb2.reverse().toString();
+  }
+
+  /**
+   * DOCUMENT ME!
+   */
+  public void traceAlignmentWithEndGaps()
+  {
+    // Find the maximum score along the rhs or bottom row
+    float max = -Float.MAX_VALUE;
+
+    for (int i = 0; i < seq1.length; i++)
+    {
+      if (score[i][seq2.length - 1] > max)
+      {
+        max = score[i][seq2.length - 1];
+        maxi = i;
+        maxj = seq2.length - 1;
+      }
+    }
+
+    for (int j = 0; j < seq2.length; j++)
+    {
+      if (score[seq1.length - 1][j] > max)
+      {
+        max = score[seq1.length - 1][j];
+        maxi = seq1.length - 1;
+        maxj = j;
+      }
+    }
+
+    int i = maxi;
+    int j = maxj;
+    int trace;
+    maxscore = score[i][j] / 10f;
+
+    //prepare trailing gaps
+    while ((i < seq1.length - 1) || (j < seq2.length - 1))
+    {
+      i++;
+      j++;
+    }
+    seq1end = i + 1;
+    seq2end = j + 1;
+
+    aseq1 = new int[seq1.length + seq2.length];
+    aseq2 = new int[seq1.length + seq2.length];
+
+    StringBuilder sb1 = new StringBuilder(aseq1.length);
+    StringBuilder sb2 = new StringBuilder(aseq2.length);
+
+    count = (seq1.length + seq2.length) - 1;
+
+    //get trailing gaps
+    while ((i >= seq1.length) || (j >= seq2.length))
+    {
+      if (i >= seq1.length)
+      {
+       aseq1[count] = GAP_INDEX;
+       sb1.append("-");
+       aseq2[count] = seq2[j];
+       sb2.append(s2str.charAt(j));
+      } else if (j >= seq2.length) {
+       aseq1[count] = seq1[i];
+       sb1.append(s1str.charAt(i));
+       aseq2[count] = GAP_INDEX;
+       sb2.append("-");
+      }
+      i--;
+      j--;
+    }
+
+    while (i > 0 && j > 0)
+    {
+      aseq1[count] = seq1[i];
+      sb1.append(s1str.charAt(i));
+      aseq2[count] = seq2[j];
+      sb2.append(s2str.charAt(j));
+
+      trace = findTrace(i, j);
+
+      if (trace == 0)
+      {
+        i--;
+        j--;
+      }
+      else if (trace == 1)
+      {
+        j--;
+        aseq1[count] = GAP_INDEX;
+        sb1.replace(sb1.length() - 1, sb1.length(), "-");
+      }
+      else if (trace == -1)
+      {
+        i--;
+        aseq2[count] = GAP_INDEX;
+        sb2.replace(sb2.length() - 1, sb2.length(), "-");
+      }
+
+      count--;
+    }
+
+    seq1start = i + 1;
+    seq2start = j + 1;
+
+    aseq1[count] = seq1[i];
+    sb1.append(s1str.charAt(i));
+    aseq2[count] = seq2[j];
+    sb2.append(s2str.charAt(j));
+
+    //get initial gaps
+    while (j > 0 || i > 0)
+    {
+      if (j > 0)
+      {
+       j--;
+       sb1.append("-");
+       sb2.append(s2str.charAt(j));
+      } else if (i > 0) {
+       i--;
+       sb1.append(s1str.charAt(i));
+       sb2.append("-");
+      }
+    }
+
     /*
      * we built the character strings backwards, so now
      * reverse them to convert to sequence strings
@@ -635,25 +818,26 @@ public class AlignSeq
   {
     int n = seq1.length;
     int m = seq2.length;
-
+    final int GAP_EX_COST=GAP_EXTEND_COST;
+    final int GAP_OP_COST = GAP_OPEN_COST;
     // top left hand element
     score[0][0] = scoreMatrix.getPairwiseScore(s1str.charAt(0),
             s2str.charAt(0)) * 10;
-    E[0][0] = -GAP_EXTEND_COST;
+    E[0][0] = -GAP_EX_COST;
     F[0][0] = 0;
 
     // Calculate the top row first
     for (int j = 1; j < m; j++)
     {
       // What should these values be? 0 maybe
-      E[0][j] = max(score[0][j - 1] - GAP_OPEN_COST,
-              E[0][j - 1] - GAP_EXTEND_COST);
-      F[0][j] = -GAP_EXTEND_COST;
+      E[0][j] = max(score[0][j - 1] - GAP_OP_COST,
+              E[0][j - 1] - GAP_EX_COST);
+      F[0][j] = -GAP_EX_COST;
 
       float pairwiseScore = scoreMatrix.getPairwiseScore(s1str.charAt(0),
               s2str.charAt(j));
-      score[0][j] = max(pairwiseScore * 10, -GAP_OPEN_COST,
-              -GAP_EXTEND_COST);
+      score[0][j] = max(pairwiseScore * 10, -GAP_OP_COST,
+              -GAP_EX_COST);
 
       traceback[0][j] = 1;
     }
@@ -661,9 +845,9 @@ public class AlignSeq
     // Now do the left hand column
     for (int i = 1; i < n; i++)
     {
-      E[i][0] = -GAP_OPEN_COST;
-      F[i][0] = max(score[i - 1][0] - GAP_OPEN_COST,
-              F[i - 1][0] - GAP_EXTEND_COST);
+      E[i][0] = -GAP_OP_COST;
+      F[i][0] = max(score[i - 1][0] - GAP_OP_COST,
+              F[i - 1][0] - GAP_EX_COST);
 
       float pairwiseScore = scoreMatrix.getPairwiseScore(s1str.charAt(i),
               s2str.charAt(0));
@@ -676,10 +860,10 @@ public class AlignSeq
     {
       for (int j = 1; j < m; j++)
       {
-        E[i][j] = max(score[i][j - 1] - GAP_OPEN_COST,
-                E[i][j - 1] - GAP_EXTEND_COST);
-        F[i][j] = max(score[i - 1][j] - GAP_OPEN_COST,
-                F[i - 1][j] - GAP_EXTEND_COST);
+        E[i][j] = max(score[i][j - 1] - GAP_OP_COST,
+                E[i][j - 1] - GAP_EX_COST);
+        F[i][j] = max(score[i - 1][j] - GAP_OP_COST,
+                F[i - 1][j] - GAP_EX_COST);
 
         float pairwiseScore = scoreMatrix.getPairwiseScore(s1str.charAt(i),
                 s2str.charAt(j));
@@ -857,7 +1041,13 @@ public class AlignSeq
   public static AlignSeq doGlobalNWAlignment(SequenceI s1, SequenceI s2,
           String type)
   {
-    AlignSeq as = new AlignSeq(s1, s2, type);
+    return doGlobalNWAlignment(s1, s2, type, DEFAULT_OPENCOST,DEFAULT_EXTENDCOST);
+  }
+  public static AlignSeq doGlobalNWAlignment(SequenceI s1, SequenceI s2,
+          String type, int opencost,int extcost)
+  {
+  
+    AlignSeq as = new AlignSeq(s1, s2, type,opencost,extcost);
 
     as.calcScoreMatrix();
     as.traceAlignment();
@@ -1132,4 +1322,181 @@ public class AlignSeq
     }
     return redundancy;
   }
+
+  /**
+  * calculate the mean score of the alignment
+  * mean score is equal to the score of an alignmenet of two sequences with randomly shuffled AA sequence composited of the same AA as the two original sequences
+  *
+  */
+  public void meanScore()
+  {
+    int length = indelfreeAstr1.length();      //both have the same length
+    //create HashMap for counting residues in each sequence
+    HashMap<Character, Integer> seq1ResCount = new HashMap<Character, Integer>();
+    HashMap<Character, Integer> seq2ResCount = new HashMap<Character, Integer>();
+
+    // for both sequences (String indelfreeAstr1 or 2) create a key for the residue and add 1 each time its encountered
+    for (char residue: indelfreeAstr1.toCharArray())
+    {
+      seq1ResCount.putIfAbsent(residue, 0);
+      seq1ResCount.replace(residue, seq1ResCount.get(residue) + 1);
+    }
+    for (char residue: indelfreeAstr2.toCharArray())
+    {
+      seq2ResCount.putIfAbsent(residue, 0);
+      seq2ResCount.replace(residue, seq2ResCount.get(residue) + 1);
+    }
+
+    // meanscore = for each residue pair get the number of appearance and add (countA * countB * pairwiseScore(AB))
+    // divide the meanscore by the sequence length afterwards
+    float _meanscore = 0;
+    for (char resA : seq1ResCount.keySet())
+    {
+      for (char resB : seq2ResCount.keySet())
+      {
+       int countA = seq1ResCount.get(resA);
+       int countB = seq2ResCount.get(resB);
+
+        float scoreAB = scoreMatrix.getPairwiseScore(resA, resB);
+
+       _meanscore += countA *  countB * scoreAB;
+      }
+    }
+    _meanscore /= length;
+    this.meanScore = _meanscore;
+  }
+
+  public float getMeanScore()
+  {
+    return this.meanScore;
+  }
+
+  /**
+  * calculate the hypothetic max score using the self-alignment of the sequences
+  */
+  public void hypotheticMaxScore()
+  {
+    int _hmsA = 0;
+    int _hmsB = 0;
+    for (char residue: indelfreeAstr1.toCharArray())
+    {
+      _hmsA += scoreMatrix.getPairwiseScore(residue, residue);
+    }
+    for (char residue: indelfreeAstr2.toCharArray())
+    {
+      _hmsB += scoreMatrix.getPairwiseScore(residue, residue);
+    }
+    this.hypotheticMaxScore = (_hmsA < _hmsB) ? _hmsA : _hmsB; // take the lower self alignment
+
+  }
+
+  public int getHypotheticMaxScore()
+  {
+    return this.hypotheticMaxScore;
+  }
+
+  /**
+  * create strings based of astr1 and astr2 but without gaps
+  */
+  public void getIndelfreeAstr()
+  {
+    int n = astr1.length();    // both have the same length
+    for (int i = 0; i < n; i++)
+    {
+      if (Character.isLetter(astr1.charAt(i)) && Character.isLetter(astr2.charAt(i)))  // if both sequences dont have a gap -> add to indelfreeAstr
+      {
+       this.indelfreeAstr1 += astr1.charAt(i);
+       this.indelfreeAstr2 += astr2.charAt(i);
+      }
+    }
+  }
+
+  /**
+  * calculates the overall score of the alignment
+  * preprescore = sum of all scores - all penalties
+  * if preprescore < 1 ~ alignmentScore = Float.NaN    >
+  * alignmentScore = ((preprescore - meanScore) / (hypotheticMaxScore - meanScore)) * coverage
+  */
+  public void scoreAlignment()
+  {
+
+    getIndelfreeAstr();
+    meanScore();
+    hypotheticMaxScore();
+    // cannot calculate score because denominator would be zero
+    if (this.hypotheticMaxScore == this.meanScore)
+    {
+      this.alignmentScore = Float.NaN;
+      return;
+    }
+    int n = indelfreeAstr1.length();
+
+    float score = 0;
+    boolean aGapOpen = false;
+    boolean bGapOpen = false;
+    for (int i = 0; i < n; i++)
+    {
+      char char1 = indelfreeAstr1.charAt(i);
+      char char2 = indelfreeAstr2.charAt(i);
+      boolean aIsLetter = Character.isLetter(char1);
+      boolean bIsLetter = Character.isLetter(char2);
+      if (aIsLetter && bIsLetter)      // if pair -> get score
+      {
+        score += scoreMatrix.getPairwiseScore(char1, char2);
+      } else if (!aIsLetter && !bIsLetter) {   // both are gap -> skip
+      } else if ((!aIsLetter && aGapOpen) || (!bIsLetter && bGapOpen)) {       // one side gapopen -> score - gap_extend
+       score -= GAP_EXTEND_COST;
+      } else {         // no gap open -> score - gap_open
+       score -= GAP_OPEN_COST;
+      }
+      // adjust GapOpen status in both sequences
+      aGapOpen = (!aIsLetter) ? true : false;
+      bGapOpen = (!bIsLetter) ? true : false;
+    }
+
+    float preprescore = score; // if this score < 1 --> alignment score = Float.NaN
+    score = (score - this.meanScore) / (this.hypotheticMaxScore - this.meanScore);
+    int[] _max = MiscMath.findMax(new int[]{astr1.replace("-","").length(), astr2.replace("-","").length()});  // {index of max, max}
+    float coverage = (float) n / (float) _max[1];      // indelfreeAstr length / longest sequence length
+    float prescore = score;    // only debug
+    score *= coverage;
+
+    //System.out.println(String.format("prepre-score: %f, pre-score: %f, longlength: %d\nscore: %1.16f, mean: %f, max: %d", preprescore, prescore, _max[1], score, this.meanScore, this.hypotheticMaxScore));
+    float minScore = 0f;
+    this.alignmentScore = (score <= minScore) ? Float.NaN : score;
+  }
+
+  public void clear()
+  {
+    score = null;
+    alignmentScore = 0f;
+    E = null;
+    F = null;
+    traceback = null; // todo is this actually used?
+    seq1 = null;
+    seq2 = null;
+    s1 = null;
+    s2 = null;
+    s1str = null;
+    s2str = null;
+    maxi = 0;
+    maxj = 0;
+    aseq1 = null;
+    aseq2 = null;
+    astr1 = "";
+    astr2 = "";
+    indelfreeAstr1 = "";
+    indelfreeAstr2 = "";
+    seq1start = 0;
+    seq1end = 0;
+    seq2start = 0;
+    seq2end = 0;
+    count = 0;
+    maxscore = 0f;
+    meanScore = 0f;    //needed for PaSiMap
+    hypotheticMaxScore = 0;    // needed for PaSiMap
+    prev = 0;
+    StringBuffer output = new StringBuffer();
+    String type = null; // AlignSeq.PEP or AlignSeq.DNA
+  }
 }
diff --git a/src/jalview/analysis/Connectivity.java b/src/jalview/analysis/Connectivity.java
new file mode 100644 (file)
index 0000000..a4cbc3a
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.analysis;
+
+//import jalview.datamodel.AlignmentView;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.SequenceI;
+import jalview.gui.Desktop;
+import jalview.gui.JvOptionPane;
+import jalview.viewmodel.AlignmentViewport;
+
+import java.util.Comparator;
+import java.util.Hashtable;
+import java.util.HashSet;
+import java.util.TreeSet;
+
+/**
+ * @Author MorellThomas
+ */
+
+public class Connectivity
+{
+
+  /**
+   * Returns the number of unique connections for each sequence
+   * only connections with a score of above 0 count
+   * 
+   * @param av sequences
+   * @param scores alignment scores
+   *
+   * @return connectivity
+   */
+  public static Hashtable<SequenceI, Integer> getConnectivity(AlignmentViewport av, float[][] scores, byte dim) throws RuntimeException
+  {
+    boolean isSelection = av.getSelectionGroup() != null && av.getSelectionGroup().getSize() > 0;
+    SequenceI[] sequences;
+    if (isSelection)
+    {
+      sequences = (SequenceI[]) av.getAlignmentView(isSelection).getAlignmentAndHiddenColumns(av.getGapCharacter())[0];
+    } else {
+      sequences = av.getAlignment().getSequencesArray();
+    }
+
+    Hashtable<SequenceI, Integer> connectivity = new Hashtable<SequenceI, Integer>();
+    // for each unique connection
+    for (int i = 0; i < sequences.length; i++)
+    {
+      connectivity.putIfAbsent(sequences[i], 0);
+      for (int j = 0; j < i; j++)
+      {
+        connectivity.putIfAbsent(sequences[j], 0);
+       int iOld = connectivity.get(sequences[i]);
+       int jOld = connectivity.get(sequences[j]); 
+        // count the connection if its score is not NaN
+//System.out.println(String.format("%s - %s : %f", sequences[i].getName(), sequences[j].getName(), scores[i][j]));
+       if (!Float.isNaN(scores[i][j]))
+       {
+         connectivity.put(sequences[i], ++iOld);
+         connectivity.put(sequences[j], ++jOld);
+       }
+      }
+    }
+
+    // if a sequence has too few connections, abort
+    connectivity.forEach((sequence, connection) ->
+    {
+      System.out.println(String.format("%s: %d", sequence.getName(), connection));
+      if (connection < dim)
+      {
+       JvOptionPane.showInternalMessageDialog(Desktop.desktop, String.format("Insufficient number of connections for %s (%d, should be %d or more)", sequence.getName(), connection, dim), "Connectivity Error", JvOptionPane.WARNING_MESSAGE);
+       throw new ConnectivityException(sequence.getName(), connection, dim);
+      }
+    } );
+
+    return connectivity;
+  }
+
+}
diff --git a/src/jalview/analysis/ConnectivityException.java b/src/jalview/analysis/ConnectivityException.java
new file mode 100644 (file)
index 0000000..9915f2a
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.analysis;
+
+public class ConnectivityException extends RuntimeException
+{
+  private String sequence;
+  private int connection;
+  private byte dim;
+
+  public ConnectivityException(String sequence, int connection, byte dim)
+  {
+    this("Insufficient number of connections", sequence, connection, dim);
+  }
+
+  public ConnectivityException(String message, String sequence, int connection, byte dim)
+  {
+    super(String.format("%s for %s (%d, should be %d or more)", message, sequence, connection, dim));
+    this.sequence = sequence;
+    this.connection = connection;
+    this.dim = dim;
+  }
+
+  public String getSequence()
+  {
+    return sequence;
+  }
+
+  public int getConnection()
+  {
+    return connection;
+  }
+
+  public byte getDim()
+  {
+    return dim;
+  }
+
+}
index 21fa2f1..c84c69a 100644 (file)
@@ -606,7 +606,8 @@ public class Finder implements FinderI
     }
     else
     {
-      allFeatures = sf.getAllFeatures(null);
+      //allFeatures = sf.getAllFeatures(null);
+      allFeatures = sf.getAllFeatures();
     }
     // so we can check we are advancing when debugging
     long fpos = 0;
diff --git a/src/jalview/analysis/PaSiMap.java b/src/jalview/analysis/PaSiMap.java
new file mode 100755 (executable)
index 0000000..7c1c24a
--- /dev/null
@@ -0,0 +1,316 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.analysis;
+
+import jalview.api.analysis.ScoreModelI;
+import jalview.api.analysis.SimilarityParamsI;
+import jalview.bin.Console;
+import jalview.datamodel.Point;
+import jalview.datamodel.SequenceI;
+import jalview.datamodel.SequenceGroup;
+import jalview.gui.PairwiseAlignPanel;
+import jalview.math.Matrix;
+import jalview.math.MatrixI;
+import jalview.viewmodel.AlignmentViewport;
+
+import java.io.PrintStream;
+import java.util.Hashtable;
+
+/**
+ * Performs Principal Component Analysis on given sequences
+ * @AUTHOR MorellThomas 
+ */
+public class PaSiMap implements Runnable
+{
+  /*
+   * inputs
+   */
+  final private AlignmentViewport seqs;
+
+  final private ScoreModelI scoreModel;
+
+  final private byte dim = 8;
+
+  final private int openCost = 100;
+
+  final private int extendCost = 5;
+
+  /*
+   * outputs
+   */
+  final private PairwiseAlignPanel alignment;
+
+  private MatrixI pairwiseScores;
+
+  private MatrixI eigenMatrix;
+
+  /**
+   * Constructor given the sequences to compute for, the similarity model to
+   * use, and a set of parameters for sequence comparison
+   * 
+   * @param sequences
+   * @param sm
+   * @param options
+   */
+  public PaSiMap(AlignmentViewport sequences, ScoreModelI sm, PairwiseAlignPanel pap)
+  {
+    this.seqs = sequences;
+    this.scoreModel = sm;
+    this.alignment = pap;
+  }
+
+  /**
+   * Returns Eigenvalue
+   * 
+   * @param i
+   *          Index of diagonal within matrix
+   * 
+   * @return Returns value of diagonal from matrix
+   */
+  public double getEigenvalue(int i)
+  {
+    return eigenMatrix.getD()[i];
+  }
+
+  /**
+   * Returns coordinates for each datapoint
+   * 
+   * @param l
+   *          DOCUMENT ME!
+   * @param n
+   *          DOCUMENT ME!
+   * @param mm
+   *          DOCUMENT ME!
+   * @param factor ~ is 1
+   * 
+   * @return DOCUMENT ME!
+   */
+  public Point[] getComponents(int l, int n, int mm, float factor)
+  {
+    Point[] out = new Point[getHeight()];
+
+    for (int i = 0; i < out.length; i++)
+    {
+      float x = (float) component(i, l) * factor;
+      float y = (float) component(i, n) * factor;
+      float z = (float) component(i, mm) * factor;
+      out[i] = new Point(x, y, z);
+    }
+
+    return out;
+  }
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @param n
+   *          DOCUMENT ME!
+   * 
+   * @return DOCUMENT ME!
+   */
+  public double[] component(int n)
+  {
+    // n = index of eigenvector
+    double[] out = new double[getWidth()];
+
+    for (int i = 0; i < out.length; i++)
+    {
+      out[i] = component(n, i);
+    }
+
+    return out;
+  }
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @param row
+   *          DOCUMENT ME!
+   * @param n
+   *          DOCUMENT ME!
+   * 
+   * @return DOCUMENT ME!
+   */
+  double component(int row, int n)
+  {
+    return eigenMatrix.getValue(row, n);
+  }
+
+  /**
+   * Answers a formatted text report of the PaSiMap calculation results (matrices
+   * and eigenvalues) suitable for display
+   * 
+   * @return
+   */
+  public String getDetails()
+  {
+    StringBuilder sb = new StringBuilder(1024);
+    sb.append("PaSiMap calculation using ").append(scoreModel.getName())
+            .append(" sequence similarity matrix\n========\n\n");
+    PrintStream ps = wrapOutputBuffer(sb);
+
+    /*
+     * coordinates matrix, with D vector
+     */
+    sb.append(" --- Pairwise correlation coefficients ---\n");
+    pairwiseScores.print(ps, "%8.6f ");
+    ps.println();
+
+    sb.append(" --- Eigenvalues ---\n");
+    eigenMatrix.printD(ps, "%15.4e");
+    ps.println();
+
+    sb.append(" --- Coordinates ---\n");
+    eigenMatrix.print(ps, "%8.6f ");
+    ps.println();
+
+    return sb.toString();
+  }
+
+  /**
+   * Performs the PaSiMap calculation
+   *
+   * creates a new gui/PairwiseAlignPanel with the input sequences (AlignmentViewport)
+   * uses analysis/AlignSeq to creatue the pairwise alignments and calculate the AlignmentScores (float for each pair)
+   * gets all float[][] scores from the gui/PairwiseAlignPanel
+   * checks the connections for each sequence with AlignmentViewport seqs.calculateConnectivity(float[][] scores, int dim) (from analysis/Connectivity) -- throws an Exception if insufficient
+   * creates a math/MatrixI pairwiseScores of the float[][] scores
+   * copys the scores and fills the diagonal to create a symmetric matrix using math/Matrix.fillDiagonal()
+   * performs the analysis/ccAnalysis with the symmetric matrix
+   * gets the eigenmatrix and the eigenvalues using math/Matrix.tqli()
+   */
+  @Override
+  public void run()
+  {
+    try
+    {
+      //alignment = new PairwiseAlignPanel(seqs, true, 100, 5);
+      alignment.calculate();
+      float[][] scores = alignment.getAlignmentScores();       //bigger index first -- eg scores[14][13]
+
+      seqs.calculateConnectivity(scores, dim);
+
+      pairwiseScores = new Matrix(scores);
+      pairwiseScores.fillDiagonal();
+
+      eigenMatrix = pairwiseScores.copy();
+
+      ccAnalysis cc = new ccAnalysis(pairwiseScores, dim);
+      eigenMatrix = cc.run().mirrorCol();
+
+    } catch (Exception q)
+    {
+      Console.error("Error computing PaSiMap:  " + q.getMessage());
+      q.printStackTrace();
+    }
+  }
+
+  /**
+   * Returns a PrintStream that wraps (appends its output to) the given
+   * StringBuilder
+   * 
+   * @param sb
+   * @return
+   */
+  protected PrintStream wrapOutputBuffer(StringBuilder sb)
+  {
+    PrintStream ps = new PrintStream(System.out)
+    {
+      @Override
+      public void print(String x)
+      {
+        sb.append(x);
+      }
+
+      @Override
+      public void println()
+      {
+        sb.append("\n");
+      }
+    };
+    return ps;
+  }
+
+  /**
+   * Answers the N dimensions of the NxM PaSiMap matrix. This is the number of
+   * sequences involved in the pairwise score calculation.
+   * 
+   * @return
+   */
+  public int getHeight()
+  {
+    // TODO can any of seqs[] be null?
+    return eigenMatrix.height();// seqs.getSequences().length;
+  }
+
+  /**
+   * Answers the M dimensions of the NxM PaSiMap matrix. This is the number of
+   * sequences involved in the pairwise score calculation.
+   * 
+   * @return
+   */
+  public int getWidth()
+  {
+    // TODO can any of seqs[] be null?
+    return eigenMatrix.width();// seqs.getSequences().length;
+  }
+
+  /**
+   * Answers the sequence pairwise similarity scores which were the first step
+   * of the PaSiMap calculation
+   * 
+   * @return
+   */
+  public MatrixI getPairwiseScores()
+  {
+    return pairwiseScores;
+  }
+
+  public void setPairwiseScores(MatrixI m)
+  {
+    pairwiseScores = m;
+  }
+
+  public MatrixI getEigenmatrix()
+  {
+    return eigenMatrix;
+  }
+
+  public void setEigenmatrix(MatrixI m)
+  {
+    eigenMatrix = m;
+  }
+
+  public PairwiseAlignPanel getAlignments()
+  {
+    return alignment;
+  }
+
+  public String getAlignmentOutput()
+  {
+    return alignment.getAlignmentOutput();
+  }
+
+  public byte getDim()
+  {
+    return dim;
+  }
+}
diff --git a/src/jalview/analysis/ccAnalysis.java b/src/jalview/analysis/ccAnalysis.java
new file mode 100755 (executable)
index 0000000..4922a54
--- /dev/null
@@ -0,0 +1,847 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+
+/*
+* Copyright 2018-2022 Kathy Su, Kay Diederichs
+* 
+* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+* 
+* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+* 
+* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. 
+*/
+
+/**
+* Ported from https://doi.org/10.1107/S2059798317000699 by
+* @AUTHOR MorellThomas
+*/
+
+package jalview.analysis;
+
+import jalview.bin.Console;
+import jalview.math.MatrixI;
+import jalview.math.Matrix;
+import jalview.math.MiscMath;
+
+import java.lang.Math;
+import java.lang.System;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.Map.Entry;
+import java.util.TreeMap;
+
+import org.apache.commons.math3.linear.Array2DRowRealMatrix;
+import org.apache.commons.math3.linear.SingularValueDecomposition;
+
+/**
+ * A class to model rectangular matrices of double values and operations on them
+ */
+public class ccAnalysis 
+{
+  private byte dim = 0;                //dimensions
+
+  private MatrixI scoresOld;   //input scores
+
+  public ccAnalysis(MatrixI scores, byte dim)
+  {
+    // round matrix to .4f to be same as in pasimap
+    for (int i = 0; i < scores.height(); i++)
+    {
+      for (int j = 0; j < scores.width(); j++)
+      {
+       if (!Double.isNaN(scores.getValue(i,j)))
+       {
+         scores.setValue(i, j, (double) Math.round(scores.getValue(i,j) * (int) 10000) / 10000);
+       }
+      }
+    }
+    this.scoresOld = scores;
+    this.dim = dim;
+  }
+
+  /** 
+  * Initialise a distrust-score for each hypothesis (h) of hSigns
+  * distrust = conHypNum - proHypNum
+  *
+  * @param hSigns ~ hypothesis signs (+/-) for each sequence
+  * @param scores ~ input score matrix
+  *
+  * @return distrustScores
+  */
+  private int[] initialiseDistrusts(byte[] hSigns, MatrixI scores)
+  {
+    int[] distrustScores = new int[scores.width()];
+    
+    // loop over symmetric matrix
+    for (int i = 0; i < scores.width(); i++)
+    {
+      byte hASign = hSigns[i];
+      int conHypNum = 0;
+      int proHypNum = 0;
+
+      for (int j = 0; j < scores.width(); j++)
+      {
+       double cell = scores.getRow(i)[j];      // value at [i][j] in scores
+       byte hBSign = hSigns[j];
+       if (!Double.isNaN(cell))
+       {
+         byte cellSign = (byte) Math.signum(cell);     //check if sign of matrix value fits hyptohesis
+         if (cellSign == hASign * hBSign)
+         {
+           proHypNum++;
+         } else {
+           conHypNum++;
+         }
+       }
+      }
+      distrustScores[i] = conHypNum - proHypNum;       //create distrust score for each sequence
+    }
+    return distrustScores;
+  }
+
+  /**
+  * Optemise hypothesis concerning the sign of the hypothetical value for each hSigns by interpreting the pairwise correlation coefficients as scalar products
+  *
+  * @param hSigns ~ hypothesis signs (+/-)
+  * @param distrustScores
+  * @param scores ~ input score matrix
+  *
+  * @return hSigns
+  */
+  private byte[] optimiseHypothesis(byte[] hSigns, int[] distrustScores, MatrixI scores)
+  {
+    // get maximum distrust score
+    int[] maxes = MiscMath.findMax(distrustScores);
+    int maxDistrustIndex = maxes[0];
+    int maxDistrust = maxes[1];
+
+    // if hypothesis is not optimal yet
+    if (maxDistrust > 0)
+    {
+      //toggle sign for hI with maximum distrust
+      hSigns[maxDistrustIndex] *= -1;
+      // update distrust at same position
+      distrustScores[maxDistrustIndex] *= -1;
+
+      // also update distrust scores for all hI that were not changed
+      byte hASign = hSigns[maxDistrustIndex];
+      for (int NOTmaxDistrustIndex = 0; NOTmaxDistrustIndex < distrustScores.length; NOTmaxDistrustIndex++)
+      {
+       if (NOTmaxDistrustIndex != maxDistrustIndex)
+       {
+         byte hBSign = hSigns[NOTmaxDistrustIndex];
+         double cell = scores.getValue(maxDistrustIndex, NOTmaxDistrustIndex);
+
+         // distrust only changed if not NaN
+         if (!Double.isNaN(cell))
+         {
+           byte cellSign = (byte) Math.signum(cell);
+           // if sign of cell matches hypothesis decrease distrust by 2 because 1 more value supporting and 1 less contradicting
+           // else increase by 2
+           if (cellSign == hASign * hBSign)
+           {
+             distrustScores[NOTmaxDistrustIndex] -= 2;
+           } else {
+             distrustScores[NOTmaxDistrustIndex] += 2;
+           }
+         }
+       }
+      }
+      //further optimisation necessary
+      return optimiseHypothesis(hSigns, distrustScores, scores);
+
+    } else {
+      return hSigns;
+    }
+  }
+
+  /** 
+  * takes the a symmetric MatrixI as input scores which may contain Double.NaN 
+  * approximate the missing values using hypothesis optimisation 
+  *
+  * runs analysis
+  *
+  * @param scores ~ score matrix
+  *
+  * @return
+  */
+  public MatrixI run () throws Exception
+  {
+    //initialse eigenMatrix and repMatrix
+    MatrixI eigenMatrix = scoresOld.copy();
+    MatrixI repMatrix = scoresOld.copy();
+    try
+    {
+    /*
+    * Calculate correction factor for 2nd and higher eigenvalue(s).
+    * This correction is NOT needed for the 1st eigenvalue, because the
+    * unknown (=NaN) values of the matrix are approximated by presuming
+    * 1-dimensional vectors as the basis of the matrix interpretation as dot
+    * products.
+    */
+        
+    System.out.println("Input correlation matrix:");
+    eigenMatrix.print(System.out, "%1.4f ");
+
+    int matrixWidth = eigenMatrix.width(); // square matrix, so width == height
+    int matrixElementsTotal = (int) Math.pow(matrixWidth, 2);  //total number of elemts
+
+    float correctionFactor = (float) (matrixElementsTotal - eigenMatrix.countNaN()) / (float) matrixElementsTotal;
+    
+    /*
+    * Calculate hypothetical value (1-dimensional vector) h_i for each
+    * dataset by interpreting the given correlation coefficients as scalar
+    * products.
+    */
+
+    /*
+    * Memory for current hypothesis concerning sign of each h_i.
+    * List of signs for all h_i in the encoding:
+      * *  1: positive
+      * *  0: zero
+      * * -1: negative
+    * Initial hypothesis: all signs are positive.
+    */
+    byte[] hSigns = new byte[matrixWidth];
+    Arrays.fill(hSigns, (byte) 1);
+
+    //Estimate signs for each h_i by refining hypothesis on signs.
+    hSigns = optimiseHypothesis(hSigns, initialiseDistrusts(hSigns, eigenMatrix), eigenMatrix);
+
+
+    //Estimate absolute values for each h_i by determining sqrt of mean of
+    //non-NaN absolute values for every row.
+    double[] hAbs = MiscMath.sqrt(eigenMatrix.absolute().meanRow());
+
+    //Combine estimated signs with absolute values in obtain total value for
+    //each h_i.
+    double[] hValues = MiscMath.elementwiseMultiply(hSigns, hAbs);
+
+    /*Complement symmetric matrix by using the scalar products of estimated
+    *values of h_i to replace NaN-cells.
+    *Matrix positions that have estimated values
+    *(only for diagonal and upper off-diagonal values, due to the symmetry
+    *the positions of the lower-diagonal values can be inferred).
+    List of tuples (row_idx, column_idx).*/
+
+    ArrayList<int[]> estimatedPositions = new ArrayList<int[]>();
+
+    // for off-diagonal cells
+    for (int rowIndex = 0; rowIndex < matrixWidth - 1; rowIndex++)
+    {
+      for (int columnIndex = rowIndex + 1; columnIndex < matrixWidth; columnIndex++)
+      {
+       double cell = eigenMatrix.getValue(rowIndex, columnIndex);
+       if (Double.isNaN(cell))
+       {
+         //calculate scalar product as new cell value
+         cell = hValues[rowIndex] * hValues[columnIndex];
+          //fill in new value in cell and symmetric partner
+         eigenMatrix.setValue(rowIndex, columnIndex, cell);
+         eigenMatrix.setValue(columnIndex, rowIndex, cell);
+         //save positions of estimated values
+         estimatedPositions.add(new int[]{rowIndex, columnIndex});
+       }
+      }
+    }
+
+    // for diagonal cells
+    for (int diagonalIndex = 0; diagonalIndex < matrixWidth; diagonalIndex++)
+      {
+        double cell = Math.pow(hValues[diagonalIndex], 2);
+       eigenMatrix.setValue(diagonalIndex, diagonalIndex, cell);
+       estimatedPositions.add(new int[]{diagonalIndex, diagonalIndex});
+      }
+
+    /*Refine total values of each h_i:
+    *Initialise h_values of the hypothetical non-existant previous iteration
+    *with the correct format but with impossible values.
+     Needed for exit condition of otherwise endless loop.*/
+    System.out.print("initial values: [ ");
+    for (double h : hValues)
+    {
+      System.out.print(String.format("%1.4f, ", h));
+    }
+    System.out.println(" ]");
+
+
+    double[] hValuesOld = new double[matrixWidth];
+
+    int iterationCount = 0;
+
+    // repeat unitl values of h do not significantly change anymore
+    while (true)
+    {
+      for (int hIndex = 0; hIndex < matrixWidth; hIndex++)
+      {
+       double newH = Arrays.stream(MiscMath.elementwiseMultiply(hValues, eigenMatrix.getRow(hIndex))).sum() / Arrays.stream(MiscMath.elementwiseMultiply(hValues, hValues)).sum();
+       hValues[hIndex] = newH;
+      }
+
+      System.out.print(String.format("iteration %d: [ ", iterationCount));
+      for (double h : hValues)
+      {
+       System.out.print(String.format("%1.4f, ", h));
+      }
+      System.out.println(" ]");
+
+      //update values of estimated positions
+      for (int[] pair : estimatedPositions)    // pair ~ row, col
+      {
+        double newVal = hValues[pair[0]] * hValues[pair[1]];
+       eigenMatrix.setValue(pair[0], pair[1], newVal);
+       eigenMatrix.setValue(pair[1], pair[0], newVal);
+      }
+
+      iterationCount++;
+
+      //exit loop as soon as new values are similar to the last iteration
+      if (MiscMath.allClose(hValues, hValuesOld, 0d, 1e-05d, false))
+      {
+        break;
+      }
+
+      //save hValues for comparison in the next iteration
+      System.arraycopy(hValues, 0, hValuesOld, 0, hValues.length);
+    }
+
+    //-----------------------------
+    //Use complemented symmetric matrix to calculate final representative
+    //vectors.
+
+    //Eigendecomposition.
+    eigenMatrix.tred();
+    eigenMatrix.tqli();
+
+    System.out.println("eigenmatrix");
+    eigenMatrix.print(System.out, "%8.2f");
+    System.out.println();
+    System.out.println("uncorrected eigenvalues");
+    eigenMatrix.printD(System.out, "%2.4f ");
+    System.out.println();
+
+    double[] eigenVals = eigenMatrix.getD();
+
+    TreeMap<Double, Integer> eigenPairs = new TreeMap<>(Comparator.reverseOrder());
+    for (int i = 0; i < eigenVals.length; i++)
+    {
+      eigenPairs.put(eigenVals[i], i);
+    }
+
+    // matrix of representative eigenvectors (each row is a vector)
+    double[][] _repMatrix = new double[eigenVals.length][dim];
+    double[][] _oldMatrix = new double[eigenVals.length][dim];
+    double[] correctedEigenValues = new double[dim];   
+
+    int l = 0;
+    for (Entry<Double, Integer> pair : eigenPairs.entrySet())
+    {
+      double eigenValue = pair.getKey();
+      int column = pair.getValue();
+      double[] eigenVector = eigenMatrix.getColumn(column);
+      //for 2nd and higher eigenvalues
+      if (l >= 1)
+      {
+        eigenValue /= correctionFactor;
+      }
+      correctedEigenValues[l] = eigenValue;
+      for (int j = 0; j < eigenVector.length; j++)
+      {
+       _repMatrix[j][l] = (eigenValue < 0) ? 0.0 : - Math.sqrt(eigenValue) * eigenVector[j];
+       double tmpOldScore = scoresOld.getColumn(column)[j];
+       _oldMatrix[j][dim - l - 1] = (Double.isNaN(tmpOldScore)) ? 0.0 : tmpOldScore;
+      }
+      l++;
+      if (l >= dim)
+      {
+       break;
+      }
+    }
+
+    System.out.println("correctedEigenValues");
+    MiscMath.print(correctedEigenValues, "%2.4f ");
+
+    repMatrix = new Matrix(_repMatrix);
+    repMatrix.setD(correctedEigenValues);
+    MatrixI oldMatrix = new Matrix(_oldMatrix);
+
+    MatrixI dotMatrix = repMatrix.postMultiply(repMatrix.transpose());
+    
+    double rmsd = scoresOld.rmsd(dotMatrix);
+
+    System.out.println("iteration, rmsd, maxDiff, rmsdDiff");
+    System.out.println(String.format("0, %8.5f, -, -", rmsd));
+    // Refine representative vectors by minimising sum-of-squared deviates between dotMatrix and original  score matrix
+    for (int iteration = 1; iteration < 21; iteration++)       // arbitrarily set to 20
+    {
+      MatrixI repMatrixOLD = repMatrix.copy();
+      MatrixI dotMatrixOLD = dotMatrix.copy();
+
+      // for all rows/hA in the original matrix
+      for (int hAIndex = 0; hAIndex < oldMatrix.height(); hAIndex++)
+      {
+       double[] row = oldMatrix.getRow(hAIndex);
+       double[] hA = repMatrix.getRow(hAIndex);
+       hAIndex = hAIndex;
+       //find least-squares-solution fo rdifferences between original scores and representative vectors
+       double[] hAlsm = leastSquaresOptimisation(repMatrix, scoresOld, hAIndex);
+        // update repMatrix with new hAlsm
+       for (int j = 0; j < repMatrix.width(); j++)
+       {
+         repMatrix.setValue(hAIndex, j, hAlsm[j]);
+       }
+      }
+      
+      // dot product of representative vecotrs yields a matrix with values approximating the correlation matrix
+      dotMatrix = repMatrix.postMultiply(repMatrix.transpose());
+      // calculate rmsd between approximation and correlation matrix
+      rmsd = scoresOld.rmsd(dotMatrix);
+
+      // calculate maximum change of representative vectors of current iteration
+      MatrixI diff = repMatrix.subtract(repMatrixOLD).absolute();
+      double maxDiff = 0.0;
+      for (int i = 0; i < diff.height(); i++)
+      {
+       for (int j = 0; j < diff.width(); j++)
+       {
+         maxDiff = (diff.getValue(i, j) > maxDiff) ? diff.getValue(i, j) : maxDiff;
+       }
+      }
+
+      // calculate rmsd between current and previous estimation
+      double rmsdDiff = dotMatrix.rmsd(dotMatrixOLD);
+
+      System.out.println(String.format("%d, %8.5f, %8.5f, %8.5f", iteration, rmsd, maxDiff, rmsdDiff));
+
+      if (!(Math.abs(maxDiff) > 1e-06))
+      {
+       repMatrix = repMatrixOLD.copy();
+       break;
+      }
+    }
+    
+
+    } catch (Exception q)
+    {
+      Console.error("Error computing cc_analysis:  " + q.getMessage());
+      q.printStackTrace();
+    }
+    System.out.println("final coordinates:");
+    repMatrix.print(System.out, "%1.8f ");
+    return repMatrix;
+  }
+
+  /**
+  * Create equations system using information on originally known
+  * pairwise correlation coefficients (parsed from infile) and the
+  * representative result vectors
+  *
+  * Each equation has the format:
+  * hA * hA - pairwiseCC = 0
+  * with:
+  * hA: unknown variable
+  * hB: known representative vector
+  * pairwiseCC: known pairwise correlation coefficien
+  * 
+  * The resulting equations system is overdetermined, if there are more
+  * equations than unknown elements
+  *
+  * @param x ~ unknown n-dimensional column-vector
+  * (needed for generating equations system, NOT to be specified by user).
+  * @param hAIndex ~ index of currently optimised representative result vector.
+  * @param h ~ matrix with row-wise listing of representative result vectors.
+  * @param originalRow ~ matrix-row of originally parsed pairwise correlation coefficients.
+  *
+  * @return
+  */
+  private double[] originalToEquasionSystem(double[] hA, MatrixI repMatrix, MatrixI scoresOld, int hAIndex)
+  {
+    double[] originalRow = scoresOld.getRow(hAIndex);
+    int nans = MiscMath.countNaN(originalRow);
+    double[] result = new double[originalRow.length - nans];
+
+    //for all pairwiseCC in originalRow
+    int resultIndex = 0;
+    for (int hBIndex = 0; hBIndex < originalRow.length; hBIndex++)
+    {
+      double pairwiseCC = originalRow[hBIndex];
+      // if not NaN -> create new equation and add it to the system
+      if (!Double.isNaN(pairwiseCC))
+      {
+        double[] hB = repMatrix.getRow(hBIndex);
+        result[resultIndex++] = MiscMath.sum(MiscMath.elementwiseMultiply(hA, hB)) - pairwiseCC;
+      } else {
+      }
+    }
+    return result;
+  }
+
+  /**
+  * returns the jacobian matrix
+  * @param repMatrix ~ matrix of representative vectors
+  * @param hAIndex ~ current row index
+  *
+  * @return
+  */
+  private MatrixI approximateDerivative(MatrixI repMatrix, MatrixI scoresOld, int hAIndex)
+  {
+    //hA = x0
+    double[] hA = repMatrix.getRow(hAIndex);
+    double[] f0 = originalToEquasionSystem(hA, repMatrix, scoresOld, hAIndex);
+    double[] signX0 = new double[hA.length];
+    double[] xAbs = new double[hA.length];
+    for (int i = 0; i < hA.length; i++)
+    {
+      signX0[i] = (hA[i] >= 0) ? 1 : -1;
+      xAbs[i] = (Math.abs(hA[i]) >= 1.0) ? Math.abs(hA[i]) : 1.0;
+      }
+    double rstep = Math.pow(Math.ulp(1.0), 0.5);
+
+    double[] h = new double [hA.length];
+    for (int i = 0; i < hA.length; i++)
+    {
+      h[i] = rstep * signX0[i] * xAbs[i];
+    }
+      
+    int m = f0.length;
+    int n = hA.length;
+    double[][] jTransposed = new double[n][m];
+    for (int i = 0; i < h.length; i++)
+    {
+      double[] x = new double[h.length];
+      System.arraycopy(hA, 0, x, 0, h.length);
+      x[i] += h[i];
+      double dx = x[i] - hA[i];
+      double[] df = originalToEquasionSystem(x, repMatrix, scoresOld, hAIndex);
+      for (int j = 0; j < df.length; j++)
+      {
+       df[j] -= f0[j];
+       jTransposed[i][j] = df[j] / dx;
+      }
+    }
+    MatrixI J = new Matrix(jTransposed).transpose();
+    return J;
+  }
+
+  /**
+  * norm of regularized (by alpha) least-squares solution minus Delta
+  * @param alpha
+  * @param suf
+  * @param s
+  * @param Delta
+  *
+  * @return
+  */
+  private double[] phiAndDerivative(double alpha, double[] suf, double[] s, double Delta)
+  {
+    double[] denom = MiscMath.elementwiseAdd(MiscMath.elementwiseMultiply(s, s), alpha);
+    double pNorm = MiscMath.norm(MiscMath.elementwiseDivide(suf, denom));
+    double phi = pNorm - Delta;
+    // - sum ( suf**2 / denom**3) / pNorm
+    double phiPrime = - MiscMath.sum(MiscMath.elementwiseDivide(MiscMath.elementwiseMultiply(suf, suf), MiscMath.elementwiseMultiply(MiscMath.elementwiseMultiply(denom, denom), denom))) / pNorm;
+    return new double[]{phi, phiPrime};
+  }
+
+  /**
+  * class holding the result of solveLsqTrustRegion
+  */
+  private class TrustRegion
+  {
+    private double[] step;
+    private double alpha;
+    private int iteration;
+
+    public TrustRegion(double[] step, double alpha, int iteration)
+    {
+      this.step = step;
+      this.alpha = alpha;
+      this.iteration = iteration;
+    }
+
+    public double[] getStep()
+    {
+      return this.step;
+    }
+
+    public double getAlpha()
+    {
+      return this.alpha;
+    }
+  
+    public int getIteration()
+    {
+      return this.iteration;
+    }
+  }
+
+  /**
+  * solve a trust-region problem arising in least-squares optimisation
+  * @param n ~ number of variables
+  * @param m ~ number of residuals
+  * @param uf
+  * @param s ~ singular values of J
+  * @param V ~ transpose of VT
+  * @param Delta ~ radius of a trust region
+  * @param alpha ~ initial guess for alpha
+  *
+  * @return
+  */
+  private TrustRegion solveLsqTrustRegion(int n, int m, double[] uf, double[] s, MatrixI V, double Delta, double alpha)
+  {
+    double[] suf = MiscMath.elementwiseMultiply(s, uf);
+
+    //check if J has full rank and tr Gauss-Newton step
+    boolean fullRank = false;
+    if (m >= n)
+    {
+      double threshold = s[0] * Math.ulp(1.0) * m;
+      fullRank = s[s.length - 1] > threshold;
+    }
+    if (fullRank)
+    {
+      double[] p = MiscMath.elementwiseMultiply(V.sumProduct(MiscMath.elementwiseDivide(uf, s)), -1);
+      if (MiscMath.norm(p) <= Delta)
+      {
+        TrustRegion result = new TrustRegion(p, 0.0, 0);
+        return result;
+      }
+    }
+
+    double alphaUpper = MiscMath.norm(suf) / Delta;
+    double alphaLower = 0.0;
+    if (fullRank)
+    {
+      double[] phiAndPrime = phiAndDerivative(0.0, suf, s, Delta);
+      alphaLower = - phiAndPrime[0] / phiAndPrime[1];
+    }
+
+    alpha = (!fullRank && alpha == 0.0) ? alpha = Math.max(0.001 * alphaUpper, Math.pow(alphaLower * alphaUpper, 0.5)) : alpha;
+
+    int iteration = 0;
+    while (iteration < 10)     // 10 is default max_iter
+    {
+      alpha = (alpha < alphaLower || alpha > alphaUpper) ? alpha = Math.max(0.001 * alphaUpper, Math.pow(alphaLower * alphaUpper, 0.5)) : alpha;
+      double[] phiAndPrime = phiAndDerivative(alpha, suf, s, Delta);
+      double phi = phiAndPrime[0];
+      double phiPrime = phiAndPrime[1];
+
+      alphaUpper = (phi < 0) ? alpha : alphaUpper;
+      double ratio = phi / phiPrime;
+      alphaLower = Math.max(alphaLower, alpha - ratio);
+      alpha -= (phi + Delta) * ratio / Delta;
+
+      if (Math.abs(phi) < 0.01 * Delta)        // default rtol set to 0.01
+      {
+       break;
+      }
+      iteration++;
+    }
+
+    // p = - V.dot( suf / (s**2 + alpha))
+    double[] tmp = MiscMath.elementwiseDivide(suf, MiscMath.elementwiseAdd(MiscMath.elementwiseMultiply(s, s), alpha));
+    double[] p = MiscMath.elementwiseMultiply(V.sumProduct(tmp), -1);
+
+    // Make the norm of p equal to Delta, p is changed only slightly during this.
+    // It is done to prevent p lie outside of the trust region
+    p = MiscMath.elementwiseMultiply(p, Delta / MiscMath.norm(p));
+
+    TrustRegion result = new TrustRegion(p, alpha, iteration + 1);
+    return result;
+  }
+
+  /**
+  * compute values of a quadratic function arising in least squares
+  * function: 0.5 * s.T * (J.T * J + diag) * s + g.T * s
+  *
+  * @param J ~ jacobian matrix
+  * @param g ~ gradient
+  * @param s ~ steps and rows
+  *
+  * @return
+  */
+  private double evaluateQuadratic(MatrixI J, double[] g, double[] s)
+  {
+
+    double[] Js = J.sumProduct(s);
+    double q = MiscMath.dot(Js, Js);
+    double l = MiscMath.dot(s, g);
+
+    return 0.5 * q + l;
+  }
+
+  /**
+  * update the radius of a trust region based on the cost reduction
+  *
+  * @param Delta
+  * @param actualReduction
+  * @param predictedReduction
+  * @param stepNorm
+  * @param boundHit
+  *
+  * @return
+  */
+  private double[] updateTrustRegionRadius(double Delta, double actualReduction, double predictedReduction, double stepNorm, boolean boundHit)
+  {
+    double ratio = 0;
+    if (predictedReduction > 0)
+    {
+      ratio = actualReduction / predictedReduction;
+    } else if (predictedReduction == 0 && actualReduction == 0) {
+      ratio = 1;
+    } else {
+      ratio = 0;
+    }
+
+    if (ratio < 0.25)
+    {
+      Delta = 0.25 * stepNorm;
+    } else if (ratio > 0.75 && boundHit) {
+      Delta *= 2.0;
+    }
+
+    return new double[]{Delta, ratio};
+  }
+
+  /**
+  * trust region reflective algorithm
+  * @param repMatrix ~ Matrix containing representative vectors
+  * @param scoresOld ~ Matrix containing initial observations
+  * @param index ~ current row index
+  * @param J ~ jacobian matrix
+  *
+  * @return
+  */
+  private double[] trf(MatrixI repMatrix, MatrixI scoresOld, int index, MatrixI J)
+  {
+    //hA = x0
+    double[] hA = repMatrix.getRow(index);
+    double[] f0 = originalToEquasionSystem(hA, repMatrix, scoresOld, index);
+    int nfev = 1;
+    int m = J.height();
+    int n = J.width();
+    double cost = 0.5 * MiscMath.dot(f0, f0);
+    double[] g = J.transpose().sumProduct(f0);
+    double Delta = MiscMath.norm(hA);
+    int maxNfev = hA.length * 100;
+    double alpha = 0.0;                // "Levenberg-Marquardt" parameter
+
+    double gNorm = 0;
+    boolean terminationStatus = false;
+    int iteration = 0;
+
+    while (true)
+    {
+      gNorm = MiscMath.norm(g);
+      if (terminationStatus || nfev == maxNfev)
+      {
+       break;
+      }
+      SingularValueDecomposition svd = new SingularValueDecomposition(new Array2DRowRealMatrix(J.asArray()));
+      MatrixI U = new Matrix(svd.getU().getData());
+      double[] s = svd.getSingularValues();    
+      MatrixI V = new Matrix(svd.getV().getData()).transpose();
+      double[] uf = U.transpose().sumProduct(f0);
+
+      double actualReduction = -1;
+      double[] xNew = new double[hA.length];
+      double[] fNew = new double[f0.length];
+      double costNew = 0;
+      double stepHnorm = 0;
+      
+      while (actualReduction <= 0 && nfev < maxNfev)
+      {
+        TrustRegion trustRegion = solveLsqTrustRegion(n, m, uf, s, V, Delta, alpha);
+       double[] stepH = trustRegion.getStep(); 
+       alpha = trustRegion.getAlpha();
+       int nIterations = trustRegion.getIteration();
+        double predictedReduction = - (evaluateQuadratic(J, g, stepH));        
+
+        xNew = MiscMath.elementwiseAdd(hA, stepH);
+       fNew = originalToEquasionSystem(xNew, repMatrix, scoresOld, index);
+       nfev++;
+       
+       stepHnorm = MiscMath.norm(stepH);
+
+       if (MiscMath.countNaN(fNew) > 0)
+       {
+         Delta = 0.25 * stepHnorm;
+         continue;
+       }
+
+       // usual trust-region step quality estimation
+       costNew = 0.5 * MiscMath.dot(fNew, fNew); 
+       actualReduction = cost - costNew;
+
+       double[] updatedTrustRegion = updateTrustRegionRadius(Delta, actualReduction, predictedReduction, stepHnorm, stepHnorm > (0.95 * Delta));
+       double DeltaNew = updatedTrustRegion[0];
+       double ratio = updatedTrustRegion[1];
+
+        // default ftol and xtol = 1e-8
+        boolean ftolSatisfied = actualReduction < (1e-8 * cost) && ratio > 0.25;
+        boolean xtolSatisfied = stepHnorm < (1e-8 * (1e-8 + MiscMath.norm(hA)));
+       terminationStatus = ftolSatisfied || xtolSatisfied;
+       if (terminationStatus)
+       {
+         break;
+       }
+
+       alpha *= Delta / DeltaNew;
+       Delta = DeltaNew;
+
+      }
+      if (actualReduction > 0)
+      {
+       hA = xNew;
+       f0 = fNew;
+       cost = costNew;
+
+       J = approximateDerivative(repMatrix, scoresOld, index);
+
+        g = J.transpose().sumProduct(f0);
+      } else {
+        stepHnorm = 0;
+       actualReduction = 0;
+      }
+      iteration++;
+    }
+
+    return hA;
+  }
+
+  /**
+  * performs the least squares optimisation
+  * adapted from https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.least_squares.html#scipy.optimize.least_squares
+  *
+  * @param repMatrix ~ Matrix containing representative vectors
+  * @param scoresOld ~ Matrix containing initial observations
+  * @param index ~ current row index
+  *
+  * @return
+  */
+  private double[] leastSquaresOptimisation(MatrixI repMatrix, MatrixI scoresOld, int index)
+  {
+    MatrixI J = approximateDerivative(repMatrix, scoresOld, index);
+    double[] result = trf(repMatrix, scoresOld, index, J);
+    return result;
+  }
+
+}
index 1aca4d4..7116cf3 100644 (file)
@@ -63,7 +63,6 @@ import java.util.Vector;
 
 import javax.swing.AbstractButton;
 import javax.swing.ButtonGroup;
-import javax.swing.ButtonModel;
 import javax.swing.JCheckBoxMenuItem;
 import javax.swing.JComponent;
 import javax.swing.JEditorPane;
@@ -73,6 +72,7 @@ import javax.swing.JLayeredPane;
 import javax.swing.JMenu;
 import javax.swing.JMenuItem;
 import javax.swing.JPanel;
+import javax.swing.JProgressBar;
 import javax.swing.JRadioButtonMenuItem;
 import javax.swing.JScrollPane;
 import javax.swing.SwingUtilities;
@@ -997,6 +997,14 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   }
 
   @Override
+  public JProgressBar getProgressBar(long id)
+  {
+    if (progressBar != null)
+      return progressBar.getProgressBar(id);
+    return null;
+  }
+
+  @Override
   public void registerHandler(final long id,
           final IProgressIndicatorHandler handler)
   {
index 4e22a74..80102cb 100644 (file)
  */
 package jalview.gui;
 
+import jalview.analysis.TreeBuilder;
+import jalview.analysis.scoremodels.ScoreModels;
+import jalview.analysis.scoremodels.SimilarityParams;
+import jalview.api.analysis.ScoreModelI;
+import jalview.api.analysis.SimilarityParamsI;
+import jalview.bin.Cache;
+import jalview.datamodel.SequenceGroup;
+import jalview.util.MessageManager;
+import jalview.viewmodel.AlignmentViewport;
 import java.awt.BorderLayout;
 import java.awt.Color;
 import java.awt.Component;
@@ -97,10 +106,14 @@ public class CalculationChooser extends JPanel
     
   }
 
+  private static final int MIN_PASIMAP_SELECTION = 8; 
+
   AlignFrame af;
 
   JRadioButton pca;
 
+  JRadioButton pasimap;        
+
   JRadioButton neighbourJoining;
 
   JRadioButton averageDistance;
@@ -130,6 +143,8 @@ public class CalculationChooser extends JPanel
    */
   private PCAPanel pcaPanel;
 
+  private PaSiMapPanel pasimapPanel;
+
   /**
    * Constructor
    * 
@@ -179,6 +194,10 @@ public class CalculationChooser extends JPanel
             MessageManager.getString("label.principal_component_analysis"));
     pca.setOpaque(false);
 
+    pasimap = new JRadioButton(                        // create the JRadioButton for pasimap with label.pasimap as its text
+           MessageManager.getString("label.pasimap"));
+    pasimap.setOpaque(false);
+
     neighbourJoining = new JRadioButton(
             MessageManager.getString("label.tree_calc_nj"));
     neighbourJoining.setSelected(true);
@@ -208,6 +227,14 @@ public class CalculationChooser extends JPanel
     pcaBorderless.add(pca, FlowLayout.LEFT);
     calcChoicePanel.add(pcaBorderless, FlowLayout.LEFT);
 
+    // create pasimap panel
+    JPanel pasimapBorderless = new JPanel(new FlowLayout(FlowLayout.LEFT));    // create new JPanel (button) for pasimap
+    pasimapBorderless.setBorder(
+           BorderFactory.createEmptyBorder(2, b.left, 2, b.right));    // set border (margin) for button (same as treePanel and pca)
+    pasimapBorderless.setOpaque(false);                // false -> stops every pixel inside border from being painted
+    pasimapBorderless.add(pasimap, FlowLayout.LEFT);   // add pasimap button to the JPanel
+    calcChoicePanel.add(pasimapBorderless, FlowLayout.LEFT);   // add button with border and everything to the overall ChoicePanel
+
     treePanel.add(neighbourJoining);
     treePanel.add(averageDistance);
 
@@ -215,6 +242,7 @@ public class CalculationChooser extends JPanel
 
     ButtonGroup calcTypes = new ButtonGroup();
     calcTypes.add(pca);
+    calcTypes.add(pasimap);
     calcTypes.add(neighbourJoining);
     calcTypes.add(averageDistance);
 
@@ -227,6 +255,7 @@ public class CalculationChooser extends JPanel
       }
     };
     pca.addActionListener(calcChanged);
+    pasimap.addActionListener(calcChanged);    // add the calcChanged ActionListener to pasimap --> <++> idk
     neighbourJoining.addActionListener(calcChanged);
     averageDistance.addActionListener(calcChanged);
     
@@ -357,12 +386,13 @@ public class CalculationChooser extends JPanel
      * return value of true means enabled and selected
      */
     boolean checkPca = checkEnabled(pca, size, MIN_PCA_SELECTION);
+    boolean checkPasimap = checkEnabled(pasimap, size, MIN_PASIMAP_SELECTION);         // check if pasimap is enabled and min_size is fulfilled
     boolean checkNeighbourJoining = checkEnabled(neighbourJoining, size,
             MIN_TREE_SELECTION);
     boolean checkAverageDistance = checkEnabled(averageDistance, size,
             MIN_TREE_SELECTION);
 
-    if (checkPca || checkNeighbourJoining || checkAverageDistance)
+    if (checkPca || checkPasimap || checkNeighbourJoining || checkAverageDistance)
     {
       calculate.setToolTipText(null);
       calculate.setEnabled(true);
@@ -606,6 +636,7 @@ public class CalculationChooser extends JPanel
   protected void calculate_actionPerformed()
   {
     boolean doPCA = pca.isSelected();
+    boolean doPaSiMap = pasimap.isSelected();
     String modelName = modelNames.getSelectedItem().toString();
     String ssSource = "";
     Object selectedItem = ssSourceDropdown.getSelectedItem();
@@ -617,16 +648,21 @@ public class CalculationChooser extends JPanel
     {
       params.setSecondaryStructureSource(ssSource);
     }
-    if (doPCA)
+
+    if (doPCA && !doPaSiMap)
     {
       openPcaPanel(modelName, params);
     }
+    else if (doPaSiMap && !doPCA)
+    {
+      openPasimapPanel(modelName, params);
+    }
     else
     {
       openTreePanel(modelName, params);
     }
 
-    // closeFrame();
+    closeFrame();
   }
 
   /**
@@ -698,6 +734,43 @@ public class CalculationChooser extends JPanel
   }
 
   /**
+   * Open a new PaSiMap panel on the desktop
+   * 
+   * @param modelName
+   * @param params
+   */
+  protected void openPasimapPanel(String modelName, SimilarityParamsI params)
+  {
+    AlignViewport viewport = af.getViewport();
+
+    /*
+     * gui validation shouldn't allow insufficient sequences here, but leave
+     * this check in in case this method gets exposed programmatically in future
+     */
+    if (((viewport.getSelectionGroup() != null)
+            && (viewport.getSelectionGroup().getSize() < MIN_PASIMAP_SELECTION)
+            && (viewport.getSelectionGroup().getSize() > 0))
+            || (viewport.getAlignment().getHeight() < MIN_PASIMAP_SELECTION))
+    {
+      JvOptionPane.showInternalMessageDialog(this,
+              MessageManager.formatMessage(
+                      "label.you_need_at_least_n_sequences",
+                      MIN_PASIMAP_SELECTION),
+              MessageManager
+                      .getString("label.sequence_selection_insufficient"),
+              JvOptionPane.WARNING_MESSAGE);
+      return;
+    }
+
+    /*
+     * construct the panel and kick off its calculation thread
+     */
+    pasimapPanel = new PaSiMapPanel(af.alignPanel, modelName, params);
+    new Thread(pasimapPanel).start();
+
+  }
+
+  /**
    * 
    */
   protected void closeFrame()
index bc3c0d2..90a3822 100644 (file)
@@ -20,6 +20,7 @@
  */
 package jalview.gui;
 
+import java.awt.Font;
 import java.awt.Toolkit;
 import java.awt.datatransfer.Clipboard;
 import java.awt.datatransfer.DataFlavor;
@@ -91,6 +92,15 @@ public class CutAndPasteTransfer extends GCutAndPasteTransfer
   }
 
   /**
+   * set font size of the textarea
+   * @param size
+   */
+  public void setFont(Font font)
+  {
+    textarea.setFont(font);
+  }
+  
+  /**
    * DOCUMENT ME!
    */
   public void setForInput(AlignmentViewPanel viewpanel)
@@ -206,6 +216,16 @@ public class CutAndPasteTransfer extends GCutAndPasteTransfer
     {
     }
   }
+  /**
+  * show menu for changing the font
+  * @param e
+  */
+  @Override
+  public void fontSizeMenu_actionPerformed(ActionEvent e)
+  {
+    new FontChooser(this);
+  }
 
   /**
    * DOCUMENT ME!
index bbd4dae..0de6b17 100644 (file)
@@ -2809,6 +2809,23 @@ public class Desktop extends jalview.jbgui.GDesktop
     }
   }
 
+  @Override
+  public JProgressBar getProgressBar(long id)
+  {
+    if (progressBars == null)
+      return null;
+
+    if (progressBars.get(Long.valueOf(id)) == null)
+      return null;
+    for (Component c : progressBars.get(Long.valueOf(id)).getComponents())
+    {
+      if (c.getClass() == JProgressBar.class)
+       return (JProgressBar) c;
+    }
+    return null;
+  }
+
   /*
    * (non-Javadoc)
    * 
index f532706..853d4aa 100755 (executable)
@@ -43,6 +43,8 @@ public class FontChooser extends GFontChooser
 
   TreePanel tp;
 
+  CutAndPasteTransfer cap;
+
   /*
    * The font on opening the dialog (to be restored on Cancel)
    */
@@ -109,30 +111,44 @@ public class FontChooser extends GFontChooser
     init();
   }
 
+  /**
+   * Creates a new FontChooser for a CutAndPasteTransfer
+   * @param cap
+  */
+  public FontChooser(CutAndPasteTransfer cap)
+  {
+    oldFont = new Font("Monospaced", Font.PLAIN, 12);
+    this.cap = cap;
+    init();
+  }
+
   void init()
   {
     frame = new JInternalFrame();
     frame.setFrameIcon(null);
     frame.setContentPane(this);
 
-    smoothFont.setSelected(ap.av.antiAlias);
-
-    /*
-     * Enable 'scale protein as cDNA' in a SplitFrame view. The selection is
-     * stored in the ViewStyle of both dna and protein Viewport. Also enable
-     * checkbox for copy font changes to other half of split frame.
-     */
-    boolean inSplitFrame = ap.av.getCodingComplement() != null;
-    if (inSplitFrame)
+    if (!isCapFont())
     {
-      oldComplementFont = ((AlignViewport) ap.av.getCodingComplement())
-              .getFont();
-      oldComplementSmooth = ((AlignViewport) ap.av
-              .getCodingComplement()).antiAlias;
-      scaleAsCdna.setVisible(true);
-      scaleAsCdna.setSelected(ap.av.isScaleProteinAsCdna());
-      fontAsCdna.setVisible(true);
-      fontAsCdna.setSelected(ap.av.isProteinFontAsCdna());
+      smoothFont.setSelected(ap.av.antiAlias);
+  
+      /*
+       * Enable 'scale protein as cDNA' in a SplitFrame view. The selection is
+       * stored in the ViewStyle of both dna and protein Viewport. Also enable
+       * checkbox for copy font changes to other half of split frame.
+       */
+      boolean inSplitFrame = ap.av.getCodingComplement() != null;
+      if (inSplitFrame)
+      {
+        oldComplementFont = ((AlignViewport) ap.av.getCodingComplement())
+                .getFont();
+        oldComplementSmooth = ((AlignViewport) ap.av
+                .getCodingComplement()).antiAlias;
+        scaleAsCdna.setVisible(true);
+        scaleAsCdna.setSelected(ap.av.isScaleProteinAsCdna());
+        fontAsCdna.setVisible(true);
+        fontAsCdna.setSelected(ap.av.isProteinFontAsCdna());
+      }
     }
 
     if (isTreeFont())
@@ -269,6 +285,10 @@ public class FontChooser extends GFontChooser
     return tp != null;
   }
 
+  private boolean isCapFont()
+  {
+    return cap != null;
+  }
   /**
    * DOCUMENT ME!
    */
@@ -350,6 +370,10 @@ public class FontChooser extends GFontChooser
         splitFrame.repaint();
       }
     }
+    else if (isCapFont())
+    {
+      cap.setFont(newFont);
+    }
 
     monospaced.setSelected(mw == iw);
 
index 35bd871..387236a 100644 (file)
@@ -20,6 +20,8 @@
  */
 package jalview.gui;
 
+import javax.swing.JProgressBar;
+
 /**
  * Visual progress indicator interface.
  * 
@@ -56,4 +58,11 @@ public interface IProgressIndicator
    */
   boolean operationInProgress();
 
+  /**
+   * 
+   * @param id
+   * @return progressbar mapped to id
+   */
+  public JProgressBar getProgressBar(long id);
+
 }
index 576f3b2..091f79e 100644 (file)
@@ -33,6 +33,7 @@ import java.awt.print.PrinterJob;
 
 import javax.swing.ButtonGroup;
 import javax.swing.JMenuItem;
+import javax.swing.JProgressBar;
 import javax.swing.JRadioButtonMenuItem;
 import javax.swing.event.InternalFrameAdapter;
 import javax.swing.event.InternalFrameEvent;
@@ -778,4 +779,10 @@ public class PCAPanel extends GPCAPanel
     getRotatableCanvas().ap = panel;
     PaintRefresher.Register(PCAPanel.this, panel.av.getSequenceSetId());
   }
+
+  @Override
+  public JProgressBar getProgressBar(long id)
+  {
+    return progressBar.getProgressBar(id);
+  }
 }
diff --git a/src/jalview/gui/PaSiMapPanel.java b/src/jalview/gui/PaSiMapPanel.java
new file mode 100644 (file)
index 0000000..562605e
--- /dev/null
@@ -0,0 +1,861 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.gui;
+
+import jalview.analysis.scoremodels.ScoreModels;
+import jalview.api.AlignViewportI;
+import jalview.api.analysis.ScoreModelI;
+import jalview.api.analysis.SimilarityParamsI;
+import jalview.bin.Console;
+import jalview.datamodel.Alignment;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.AlignmentView;
+import jalview.datamodel.HiddenColumns;
+import jalview.datamodel.SequenceI;
+import jalview.gui.ImageExporter.ImageWriterI;
+import jalview.gui.JalviewColourChooser.ColourChooserListener;
+import jalview.jbgui.GPaSiMapPanel;
+import jalview.math.RotatableMatrix.Axis;
+import jalview.util.ImageMaker;
+import jalview.util.MessageManager;
+import jalview.viewmodel.AlignmentViewport;
+import jalview.viewmodel.PaSiMapModel;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.print.PageFormat;
+import java.awt.print.Printable;
+import java.awt.print.PrinterException;
+import java.awt.print.PrinterJob;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.ButtonGroup;
+import javax.swing.JMenuItem;
+import javax.swing.JProgressBar;
+import javax.swing.JRadioButtonMenuItem;
+import javax.swing.event.InternalFrameAdapter;
+import javax.swing.event.InternalFrameEvent;
+
+/**
+ * The panel holding the Pairwise Similarity Map 3-D visualisation
+ */
+public class PaSiMapPanel extends GPaSiMapPanel
+        implements Runnable, IProgressIndicator
+{
+  private static final int MIN_WIDTH = 470;
+
+  private static final int MIN_HEIGHT = 250;
+
+  private final int GAP_OPEN_COST = 100;
+
+  private final int GAP_EXTEND_COST = 5;
+
+  private RotatableCanvas rc;
+
+  AlignmentPanel ap;
+
+  AlignmentViewport av;
+
+  private PaSiMapModel pasimapModel;
+
+  private int top = 0;
+
+  private IProgressIndicator progressBar;
+
+  private long progId;
+
+  private boolean working;
+
+  /**
+   * Constructor given sequence data, a similarity (or distance) score model
+   * name, and score calculation parameters
+   * 
+   * @param alignPanel
+   * @param modelName
+   * @param params
+   */
+  public PaSiMapPanel(AlignmentPanel alignPanel, String modelName,
+          SimilarityParamsI params)
+  {
+    super(8);  // dim = 8
+    this.av = alignPanel.av;
+    this.ap = alignPanel;
+    boolean nucleotide = av.getAlignment().isNucleotide();
+
+    //progressBar = new ProgressBar(statusPanel, statusBar);
+
+    addInternalFrameListener(new InternalFrameAdapter()
+    {
+      @Override
+      public void internalFrameClosed(InternalFrameEvent e)
+      {
+        close_actionPerformed();
+      }
+    });
+
+    boolean selected = av.getSelectionGroup() != null
+            && av.getSelectionGroup().getSize() > 0;
+    SequenceI[] seqs;
+    if (!selected)
+    {
+      seqs = av.getAlignment().getSequencesArray();
+    }
+    else
+    {
+      seqs = av.getSelectionGroup().getSequencesInOrder(av.getAlignment());
+    }
+
+    ScoreModelI scoreModel = ScoreModels.getInstance()
+            .getScoreModel(modelName, ap);
+    setPasimapModel(
+            new PaSiMapModel(av, seqs, nucleotide, scoreModel));
+    PaintRefresher.Register(this, av.getSequenceSetId());
+
+    setRotatableCanvas(new RotatableCanvas(alignPanel));
+    this.getContentPane().add(getRotatableCanvas(), BorderLayout.CENTER);
+
+    addKeyListener(getRotatableCanvas());
+    validate();
+  }
+
+  /**
+   * Ensure references to potentially very large objects (the PaSiMap matrices) are
+   * nulled when the frame is closed
+   */
+  protected void close_actionPerformed()
+  {
+    setPasimapModel(null);
+    if (this.rc != null)
+    {
+      this.rc.sequencePoints = null;
+      this.rc.setAxisEndPoints(null);
+      this.rc = null;
+    }
+  }
+
+  @Override
+  protected void bgcolour_actionPerformed()
+  {
+    String ttl = MessageManager.getString("label.select_background_colour");
+    ColourChooserListener listener = new ColourChooserListener()
+    {
+      @Override
+      public void colourSelected(Color c)
+      {
+        rc.setBgColour(c);
+        rc.repaint();
+      }
+    };
+    JalviewColourChooser.showColourChooser(this, ttl, rc.getBgColour(),
+            listener);
+  }
+
+  /**
+   * Calculates the PaSiMap and displays the results
+   */
+  @Override
+  public void run()
+  {
+    working = true;
+    progId = System.currentTimeMillis();
+    progressBar = this;
+    String message = MessageManager.getString("label.pasimap_recalculating");
+    if (getParent() == null)
+    {
+      progressBar = ap.alignFrame;
+      message = MessageManager.getString("label.pasimap_calculating");
+    }
+    progressBar.setProgressBar(message, progId);
+    try
+    {
+      //&! remove big seqs
+      for (SequenceI seq : av.getAlignment().getSequencesArray())
+      {
+       if (seq.getLength() > 20000)
+       {
+         //TODO add warning dialog
+         av.getAlignment().deleteSequence(seq);
+       }
+      }
+
+      PairwiseAlignPanel pap = new PairwiseAlignPanel(av, true, GAP_OPEN_COST, GAP_EXTEND_COST, false);
+System.out.println(pap != null);
+      setPairwiseAlignPanel(pap);
+      getPasimapModel().calculate(pap);
+
+      xCombobox.setSelectedIndex(0);
+      yCombobox.setSelectedIndex(1);
+      zCombobox.setSelectedIndex(2);
+
+      getPasimapModel().updateRc(getRotatableCanvas());
+      // rc.invalidate();
+      setTop(getPasimapModel().getTop());
+
+    } catch (OutOfMemoryError er)
+    {
+      new OOMWarning("calculating PaSiMap", er);
+      working = false;
+      return;
+    } finally
+    {
+      progressBar.setProgressBar("", progId);
+    }
+
+    repaint();
+    if (getParent() == null)
+    {
+      Desktop.addInternalFrame(this,
+              MessageManager.formatMessage("label.calc_title", "PaSiMap",
+                      getPasimapModel().getScoreModelName()),
+              475, 450);
+      this.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
+    }
+    working = false;
+  }
+
+  /**
+   * Updates the PaSiMap display after a change of component to use for x, y or z
+   * axis
+   */
+  @Override
+  protected void doDimensionChange()
+  {
+    if (getTop() == 0)
+    {
+      return;
+    }
+
+    int dim1 = getTop() - xCombobox.getSelectedIndex();
+    int dim2 = getTop() - yCombobox.getSelectedIndex();
+    int dim3 = getTop() - zCombobox.getSelectedIndex();
+    getPasimapModel().updateRcView(dim1, dim2, dim3);
+    getRotatableCanvas().resetView();
+  }
+
+  /**
+   * Sets the selected checkbox item index for PaSiMap dimension (1, 2, 3...) for
+   * the given axis (X/Y/Z)
+   * 
+   * @param index
+   * @param axis
+   */
+  public void setSelectedDimensionIndex(int index, Axis axis)
+  {
+    switch (axis)
+    {
+    case X:
+      xCombobox.setSelectedIndex(index);
+      break;
+    case Y:
+      yCombobox.setSelectedIndex(index);
+      break;
+    case Z:
+      zCombobox.setSelectedIndex(index);
+      break;
+    default:
+    }
+  }
+
+  @Override
+  protected void outputValues_actionPerformed()
+  {
+    CutAndPasteTransfer cap = new CutAndPasteTransfer();
+    try
+    {
+      cap.setText(getPasimapModel().getDetails());
+      Desktop.addInternalFrame(cap,
+              MessageManager.getString("label.pasimap_details"), 500, 500);
+    } catch (OutOfMemoryError oom)
+    {
+      new OOMWarning("opening PaSiMap details", oom);
+      cap.dispose();
+    }
+  }
+
+  @Override
+  protected void showLabels_actionPerformed()
+  {
+    getRotatableCanvas().showLabels(showLabels.getState());
+  }
+
+  @Override
+  protected void print_actionPerformed()
+  {
+    PaSiMapPrinter printer = new PaSiMapPrinter();
+    printer.start();
+  }
+
+  /**
+   * If available, shows the data which formed the inputs for the PaSiMap as a new
+   * alignment
+   */
+  @Override
+  public void originalSeqData_actionPerformed()
+  {
+    // JAL-2647 disabled after load from project (until save to project done)
+    if (getPasimapModel().getInputData() == null)
+    {
+      Console.info(
+              "Unexpected call to originalSeqData_actionPerformed - should have hidden this menu action.");
+      return;
+    }
+    // decide if av alignment is sufficiently different to original data to
+    // warrant a new window to be created
+    // create new alignment window with hidden regions (unhiding hidden regions
+    // yields unaligned seqs)
+    // or create a selection box around columns in alignment view
+    // test Alignment(SeqCigar[])
+    char gc = '-';
+    try
+    {
+      // we try to get the associated view's gap character
+      // but this may fail if the view was closed...
+      gc = av.getGapCharacter();
+    } catch (Exception ex)
+    {
+    }
+
+    Object[] alAndColsel = getPasimapModel().getInputData()
+            .getAlignmentView(false).getAlignmentAndHiddenColumns(gc);
+
+    if (alAndColsel != null && alAndColsel[0] != null)
+    {
+      // AlignmentOrder origorder = new AlignmentOrder(alAndColsel[0]);
+
+      AlignmentI al = new Alignment((SequenceI[]) alAndColsel[0]);
+      AlignmentI dataset = (av != null && av.getAlignment() != null)
+              ? av.getAlignment().getDataset()
+              : null;
+      if (dataset != null)
+      {
+        al.setDataset(dataset);
+      }
+
+      if (true)
+      {
+        // make a new frame!
+        AlignFrame af = new AlignFrame(al, (HiddenColumns) alAndColsel[1],
+                AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
+
+        // >>>This is a fix for the moment, until a better solution is
+        // found!!<<<
+        // af.getFeatureRenderer().transferSettings(alignFrame.getFeatureRenderer());
+
+        // af.addSortByOrderMenuItem(ServiceName + " Ordering",
+        // msaorder);
+
+        Desktop.addInternalFrame(af, MessageManager.formatMessage(
+                "label.original_data_for_params", new String[]
+                { this.title }), AlignFrame.DEFAULT_WIDTH,
+                AlignFrame.DEFAULT_HEIGHT);
+      }
+    }
+    /*
+     * CutAndPasteTransfer cap = new CutAndPasteTransfer(); for (int i = 0; i <
+     * seqs.length; i++) { cap.appendText(new jalview.util.Format("%-" + 15 +
+     * "s").form( seqs[i].getName())); cap.appendText(" " + seqstrings[i] +
+     * "\n"); }
+     * 
+     * Desktop.addInternalFrame(cap, "Original Data", 400, 400);
+     */
+  }
+
+  class PaSiMapPrinter extends Thread implements Printable
+  {
+    @Override
+    public void run()
+    {
+      PrinterJob printJob = PrinterJob.getPrinterJob();
+      PageFormat defaultPage = printJob.defaultPage();
+      PageFormat pf = printJob.pageDialog(defaultPage);
+
+      if (defaultPage == pf)
+      {
+        /*
+         * user cancelled
+         */
+        return;
+      }
+
+      printJob.setPrintable(this, pf);
+
+      if (printJob.printDialog())
+      {
+        try
+        {
+          printJob.print();
+        } catch (Exception PrintException)
+        {
+          PrintException.printStackTrace();
+        }
+      }
+    }
+
+    @Override
+    public int print(Graphics pg, PageFormat pf, int pi)
+            throws PrinterException
+    {
+      pg.translate((int) pf.getImageableX(), (int) pf.getImageableY());
+
+      getRotatableCanvas().drawBackground(pg);
+      getRotatableCanvas().drawScene(pg);
+      if (getRotatableCanvas().drawAxes)
+      {
+        getRotatableCanvas().drawAxes(pg);
+      }
+
+      if (pi == 0)
+      {
+        return Printable.PAGE_EXISTS;
+      }
+      else
+      {
+        return Printable.NO_SUCH_PAGE;
+      }
+    }
+  }
+
+  public void makePaSiMapImage(ImageMaker.TYPE type)  throws Exception
+  {
+    int width = getRotatableCanvas().getWidth();
+    int height = getRotatableCanvas().getHeight();
+    ImageWriterI writer = new ImageWriterI()
+    {
+      @Override
+      public void exportImage(Graphics g) throws Exception
+      {
+        RotatableCanvas canvas = getRotatableCanvas();
+        canvas.drawBackground(g);
+        canvas.drawScene(g);
+        if (canvas.drawAxes)
+        {
+          canvas.drawAxes(g);
+        }
+      }
+    };
+    String pasimap = MessageManager.getString("label.pasimap");
+    ImageExporter exporter = new ImageExporter(writer, null, type, pasimap);
+    exporter.doExport(null, this, width, height, pasimap);
+  }
+
+  @Override
+  protected void viewMenu_menuSelected()
+  {
+    buildAssociatedViewMenu();
+  }
+
+  /**
+   * Builds the menu showing the choice of possible views (for the associated
+   * sequence data) to which the PaSiMap may be linked
+   */
+  void buildAssociatedViewMenu()
+  {
+    AlignmentPanel[] aps = PaintRefresher
+            .getAssociatedPanels(av.getSequenceSetId());
+    if (aps.length == 1 && getRotatableCanvas().av == aps[0].av)
+    {
+      associateViewsMenu.setVisible(false);
+      return;
+    }
+
+    associateViewsMenu.setVisible(true);
+
+    if ((viewMenu
+            .getItem(viewMenu.getItemCount() - 2) instanceof JMenuItem))
+    {
+      viewMenu.insertSeparator(viewMenu.getItemCount() - 1);
+    }
+
+    associateViewsMenu.removeAll();
+
+    JRadioButtonMenuItem item;
+    ButtonGroup buttonGroup = new ButtonGroup();
+    int iSize = aps.length;
+
+    for (int i = 0; i < iSize; i++)
+    {
+      final AlignmentPanel panel = aps[i];
+      item = new JRadioButtonMenuItem(panel.av.getViewName(),
+              panel.av == getRotatableCanvas().av);
+      buttonGroup.add(item);
+      item.addActionListener(new ActionListener()
+      {
+        @Override
+        public void actionPerformed(ActionEvent evt)
+        {
+          selectAssociatedView(panel);
+        }
+      });
+
+      associateViewsMenu.add(item);
+    }
+
+    final JRadioButtonMenuItem itemf = new JRadioButtonMenuItem(
+            "All Views");
+
+    buttonGroup.add(itemf);
+
+    itemf.setSelected(getRotatableCanvas().isApplyToAllViews());
+    itemf.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent evt)
+      {
+        getRotatableCanvas().setApplyToAllViews(itemf.isSelected());
+      }
+    });
+    associateViewsMenu.add(itemf);
+
+  }
+
+  /*
+   * (non-Javadoc)
+   * 
+   * @see
+   * jalview.jbgui.GPaSiMapPanel#outputPoints_actionPerformed(java.awt.event.ActionEvent
+   * )
+   */
+  @Override
+  protected void outputPoints_actionPerformed()
+  {
+    CutAndPasteTransfer cap = new CutAndPasteTransfer();
+    try
+    {
+      cap.setText(getPasimapModel().getPointsasCsv(false,
+              xCombobox.getSelectedIndex(), yCombobox.getSelectedIndex(),
+              zCombobox.getSelectedIndex()));
+      Desktop.addInternalFrame(cap, MessageManager
+              .formatMessage("label.points_for_params", new String[]
+              { this.getTitle() }), 500, 500);
+    } catch (OutOfMemoryError oom)
+    {
+      new OOMWarning("exporting PaSiMap points", oom);
+      cap.dispose();
+    }
+  }
+
+  /*
+   * (non-Javadoc)
+   * 
+   * @see
+   * jalview.jbgui.GPaSiMapPanel#outputProjPoints_actionPerformed(java.awt.event
+   * .ActionEvent)
+   */
+  @Override
+  protected void outputProjPoints_actionPerformed()
+  {
+    CutAndPasteTransfer cap = new CutAndPasteTransfer();
+    try
+    {
+      cap.setText(getPasimapModel().getPointsasCsv(true,
+              xCombobox.getSelectedIndex(), yCombobox.getSelectedIndex(),
+              zCombobox.getSelectedIndex()));
+      Desktop.addInternalFrame(cap, MessageManager.formatMessage(
+              "label.transformed_points_for_params", new String[]
+              { this.getTitle() }), 500, 500);
+    } catch (OutOfMemoryError oom)
+    {
+      new OOMWarning("exporting transformed PaSiMap points", oom);
+      cap.dispose();
+    }
+  }
+
+  /*
+   * (non-Javadoc)
+   * 
+   * @see
+   * jalview.jbgui.GPaSiMapPanel#outputAlignment_actionPerformed(java.awt.event
+   * .ActionEvent)
+   */
+  @Override
+  protected void outputAlignment_actionPerformed()
+  {
+    CutAndPasteTransfer cap = new CutAndPasteTransfer();
+    try
+    {
+      cap.setText(getPasimapModel().getAlignmentOutput());
+      Desktop.addInternalFrame(cap, MessageManager.formatMessage(
+       "label.pairwise_alignment_for_params", new String[] { this.getTitle() }), 500, 500);
+    } catch (OutOfMemoryError oom)
+    {
+      new OOMWarning("exporting pairwise alignments", oom);
+      cap.dispose();
+    }
+  }
+
+  /*
+   * (non-Javadoc)
+   * 
+   * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
+   */
+  @Override
+  public void setProgressBar(String message, long id)
+  {
+    progressBar.setProgressBar(message, id);
+    // if (progressBars == null)
+    // {
+    // progressBars = new Hashtable();
+    // progressBarHandlers = new Hashtable();
+    // }
+    //
+    // JPanel progressPanel;
+    // Long lId = Long.valueOf(id);
+    // GridLayout layout = (GridLayout) statusPanel.getLayout();
+    // if (progressBars.get(lId) != null)
+    // {
+    // progressPanel = (JPanel) progressBars.get(Long.valueOf(id));
+    // statusPanel.remove(progressPanel);
+    // progressBars.remove(lId);
+    // progressPanel = null;
+    // if (message != null)
+    // {
+    // statusBar.setText(message);
+    // }
+    // if (progressBarHandlers.contains(lId))
+    // {
+    // progressBarHandlers.remove(lId);
+    // }
+    // layout.setRows(layout.getRows() - 1);
+    // }
+    // else
+    // {
+    // progressPanel = new JPanel(new BorderLayout(10, 5));
+    //
+    // JProgressBar progressBar = new JProgressBar();
+    // progressBar.setIndeterminate(true);
+    //
+    // progressPanel.add(new JLabel(message), BorderLayout.WEST);
+    // progressPanel.add(progressBar, BorderLayout.CENTER);
+    //
+    // layout.setRows(layout.getRows() + 1);
+    // statusPanel.add(progressPanel);
+    //
+    // progressBars.put(lId, progressPanel);
+    // }
+    // // update GUI
+    // // setMenusForViewport();
+    // validate();
+  }
+
+  /*
+   * make the progressBar determinate and update its progress
+  */
+  public void updateProgressBar(int lengthOfTask, int progress)
+  {
+    JProgressBar pBar = progressBar.getProgressBar(progId);
+    if (pBar.isIndeterminate())
+    {
+      pBar.setMaximum(lengthOfTask);
+      pBar.setValue(0);
+      pBar.setIndeterminate(false);
+    }
+    updateProgressBar(progress);
+  }
+  public void updateProgressBar(int progress)
+  {
+    JProgressBar pBar = progressBar.getProgressBar(progId);
+    pBar.setValue(progress);
+    pBar.repaint();
+  }
+
+  //&!
+  public void setPairwiseAlignPanel(PairwiseAlignPanel pap)
+  {
+    pap.addPropertyChangeListener(new PropertyChangeListener()
+    {
+      @Override
+      public void propertyChange(PropertyChangeEvent pcEvent)
+      {
+       if (PairwiseAlignPanel.PROGRESS.equals(pcEvent.getPropertyName()))
+       {
+         updateProgressBar((int) pcEvent.getNewValue());
+       } else if (PairwiseAlignPanel.TOTAL.equals(pcEvent.getPropertyName())) {
+         updateProgressBar((int) pcEvent.getNewValue(), 0);
+       }
+      }
+    });
+  }
+
+  @Override
+  public void registerHandler(final long id,
+          final IProgressIndicatorHandler handler)
+  {
+    progressBar.registerHandler(id, handler);
+    // if (progressBarHandlers == null ||
+    // !progressBars.contains(Long.valueOf(id)))
+    // {
+    // throw new
+    // Error(MessageManager.getString("error.call_setprogressbar_before_registering_handler"));
+    // }
+    // progressBarHandlers.put(Long.valueOf(id), handler);
+    // final JPanel progressPanel = (JPanel) progressBars.get(Long.valueOf(id));
+    // if (handler.canCancel())
+    // {
+    // JButton cancel = new JButton(
+    // MessageManager.getString("action.cancel"));
+    // final IProgressIndicator us = this;
+    // cancel.addActionListener(new ActionListener()
+    // {
+    //
+    // @Override
+    // public void actionPerformed(ActionEvent e)
+    // {
+    // handler.cancelActivity(id);
+    // us.setProgressBar(MessageManager.formatMessage("label.cancelled_params",
+    // new String[]{((JLabel) progressPanel.getComponent(0)).getText()}), id);
+    // }
+    // });
+    // progressPanel.add(cancel, BorderLayout.EAST);
+    // }
+  }
+
+  /**
+   * 
+   * @return true if any progress bars are still active
+   */
+  @Override
+  public boolean operationInProgress()
+  {
+    return progressBar.operationInProgress();
+  }
+
+  @Override
+  protected void resetButton_actionPerformed()
+  {
+    int t = getTop();
+    setTop(0); // ugly - prevents dimensionChanged events from being processed
+    xCombobox.setSelectedIndex(0);
+    yCombobox.setSelectedIndex(1);
+    setTop(t);
+    zCombobox.setSelectedIndex(2);
+  }
+
+  /**
+   * Answers true if PaSiMap calculation is in progress, else false
+   * 
+   * @return
+   */
+  public boolean isWorking()
+  {
+    return working;
+  }
+
+  /**
+   * Answers the selected checkbox item index for PaSiMap dimension for the X, Y or
+   * Z axis of the display
+   * 
+   * @param axis
+   * @return
+   */
+  public int getSelectedDimensionIndex(Axis axis)
+  {
+    switch (axis)
+    {
+    case X:
+      return xCombobox.getSelectedIndex();
+    case Y:
+      return yCombobox.getSelectedIndex();
+    default:
+      return zCombobox.getSelectedIndex();
+    }
+  }
+
+  public void setShowLabels(boolean show)
+  {
+    showLabels.setSelected(show);
+  }
+
+  /**
+   * Sets the input data used to calculate the PaSiMap. This is provided for
+   * 'restore from project', which does not currently support this (AL-2647), so
+   * sets the value to null, and hides the menu option for "Input Data...". J
+   * 
+   * @param data
+   */
+  public void setInputData(AlignmentViewport data)
+  {
+    getPasimapModel().setInputData(data);
+    originalSeqData.setVisible(data != null);
+  }
+
+  public AlignViewportI getAlignViewport()
+  {
+    return av;
+  }
+
+  public PaSiMapModel getPasimapModel()
+  {
+    return pasimapModel;
+  }
+
+  public void setPasimapModel(PaSiMapModel pasimapModel)
+  {
+    this.pasimapModel = pasimapModel;
+  }
+
+  public RotatableCanvas getRotatableCanvas()
+  {
+    return rc;
+  }
+
+  public void setRotatableCanvas(RotatableCanvas rc)
+  {
+    this.rc = rc;
+  }
+
+  public int getTop()
+  {
+    return top;
+  }
+
+  public void setTop(int top)
+  {
+    this.top = top;
+  }
+
+  /**
+   * set the associated view for this PaSiMap.
+   * 
+   * @param panel
+   */
+  public void selectAssociatedView(AlignmentPanel panel)
+  {
+    getRotatableCanvas().setApplyToAllViews(false);
+
+    ap = panel;
+    av = panel.av;
+
+    getRotatableCanvas().av = panel.av;
+    getRotatableCanvas().ap = panel;
+    PaintRefresher.Register(PaSiMapPanel.this, panel.av.getSequenceSetId());
+  }
+
+  @Override
+  public JProgressBar getProgressBar(long id)
+  {
+    return progressBar.getProgressBar(id);
+  }
+}
index b5b6ffc..af7913b 100755 (executable)
@@ -28,9 +28,12 @@ import jalview.datamodel.SequenceI;
 import jalview.jbgui.GPairwiseAlignPanel;
 import jalview.util.MessageManager;
 import jalview.viewmodel.AlignmentViewport;
+import jalview.math.MiscMath;
 
+import java.beans.PropertyChangeListener;
 import java.awt.event.ActionEvent;
 import java.util.Vector;
+import javax.swing.event.SwingPropertyChangeSupport;
 
 /**
  * DOCUMENT ME!
@@ -43,56 +46,113 @@ public class PairwiseAlignPanel extends GPairwiseAlignPanel
 
   private static final String DASHES = "---------------------\n";
 
+  private float[][] scores;
+
+  private float[][] alignmentScores;   // scores used by PaSiMap
+
+  private int GAP_OPEN_COST;
+
+  private int GAP_EXTEND_COST;
+
   AlignmentViewport av;
 
   Vector<SequenceI> sequences;
 
+  private String alignmentOutput;
+
+  private boolean suppressTextbox;
+  private boolean discardAlignments;
+
+  private boolean endGaps;
+
+  // for listening
+  public static final String TOTAL = "total";
+
+  public static final String PROGRESS = "progress";
+
+  private int total;
+
+  private int progress;
+
   /**
    * Creates a new PairwiseAlignPanel object.
    * 
    * @param viewport
    *          DOCUMENT ME!
+   * @param endGaps ~ toggle gaps and the beginning and end of sequences
    */
   public PairwiseAlignPanel(AlignmentViewport viewport)
   {
+    this(viewport, false, 120, 20, true);      // default penalties used in AlignSeq
+  }
+  public PairwiseAlignPanel(AlignmentViewport viewport, boolean endGaps, int gapOpenCost, int gapExtendCost)
+  {
+    this(viewport, endGaps, gapOpenCost, gapExtendCost, true);
+  }
+  public PairwiseAlignPanel(AlignmentViewport viewport, boolean endGaps, int gapOpenCost, int gapExtendCost, boolean run)
+  {
     super();
     this.av = viewport;
+    this.GAP_OPEN_COST = gapOpenCost;
+    this.GAP_EXTEND_COST = gapExtendCost;
+    this.endGaps = endGaps;
+    this.total = MiscMath.combinations(av.getAlignment().getHeight(), 2);
+    
+    if (run)
+      calculate();
+System.out.println("Creating pap");
+  }
+
+  public void calculate()
+  {
+
+    SequenceGroup selectionGroup = av.getSelectionGroup();
+    StringBuilder sb = new StringBuilder(1024);
 
     sequences = new Vector<SequenceI>();
 
-    SequenceGroup selectionGroup = viewport.getSelectionGroup();
     boolean isSelection = selectionGroup != null
             && selectionGroup.getSize() > 0;
-    AlignmentView view = viewport.getAlignmentView(isSelection);
-    // String[] seqStrings = viewport.getViewAsString(true);
+    AlignmentView view = av.getAlignmentView(isSelection);
+    // String[] seqStrings = av.getViewAsString(true);
     String[] seqStrings = view
-            .getSequenceStrings(viewport.getGapCharacter());
+            .getSequenceStrings(av.getGapCharacter());
 
     SequenceI[] seqs;
     if (isSelection)
     {
       seqs = (SequenceI[]) view
-              .getAlignmentAndHiddenColumns(viewport.getGapCharacter())[0];
+              .getAlignmentAndHiddenColumns(av.getGapCharacter())[0];
     }
     else
     {
       seqs = av.getAlignment().getSequencesArray();
     }
 
-    String type = (viewport.getAlignment().isNucleotide()) ? AlignSeq.DNA
+    String type = (av.getAlignment().isNucleotide()) ? AlignSeq.DNA
             : AlignSeq.PEP;
 
     float[][] scores = new float[seqs.length][seqs.length];
+    float[][] alignmentScores = new float[seqs.length][seqs.length];
     double totscore = 0D;
     int count = seqs.length;
     boolean first = true;
 
+    progress = 0;
+    firePropertyChange(TOTAL, 0, total);
+
+    suppressTextbox = count<10;
+    discardAlignments = count<15;
+
     for (int i = 1; i < count; i++)
     {
+      // fill diagonal alignmentScores with Float.NaN
+      alignmentScores[i - 1][i - 1] = Float.NaN;
       for (int j = 0; j < i; j++)
       {
         AlignSeq as = new AlignSeq(seqs[i], seqStrings[i], seqs[j],
-                seqStrings[j], type);
+                seqStrings[j], type, GAP_OPEN_COST, GAP_EXTEND_COST);
 
         if (as.s1str.length() == 0 || as.s2str.length() == 0)
         {
@@ -100,28 +160,68 @@ public class PairwiseAlignPanel extends GPairwiseAlignPanel
         }
 
         as.calcScoreMatrix();
-        as.traceAlignment();
+       if (endGaps)
+       {
+          as.traceAlignmentWithEndGaps();
+       } else {
+         as.traceAlignment();
+       }
+       as.scoreAlignment();
 
         if (!first)
         {
           jalview.bin.Console.outPrintln(DASHES);
           textarea.append(DASHES);
+         sb.append(DASHES);
         }
         first = false;
-        as.printAlignment(System.out);
+        if (discardAlignments) {
+          as.printAlignment(System.out);
+       }
         scores[i][j] = as.getMaxScore() / as.getASeq1().length;
+        alignmentScores[i][j] = as.getAlignmentScore();
         totscore = totscore + scores[i][j];
 
-        textarea.append(as.getOutput());
-        sequences.add(as.getAlignedSeq1());
-        sequences.add(as.getAlignedSeq2());
+       if (suppressTextbox)
+       {
+          textarea.append(as.getOutput());
+         sb.append(as.getOutput());
+       }
+       if (discardAlignments)
+       {
+          sequences.add(as.getAlignedSeq1());
+          sequences.add(as.getAlignedSeq2());
+       }
+
+       firePropertyChange(PROGRESS, progress, ++progress);
       }
     }
+    alignmentScores[count - 1][count - 1] = Float.NaN;
+
+    this.scores = scores;
+    this.alignmentScores = alignmentScores;
 
     if (count > 2)
     {
       printScoreMatrix(seqs, scores, totscore);
     }
+
+    alignmentOutput = sb.toString();
+  }
+
+  public float[][] getScores()
+  {
+    return this.scores;
+  }
+
+  public float[][] getAlignmentScores()
+  {
+    return this.alignmentScores;
+  }
+
+  public String getAlignmentOutput()
+  {
+    return this.alignmentOutput;
   }
 
   /**
@@ -192,4 +292,14 @@ public class PairwiseAlignPanel extends GPairwiseAlignPanel
             MessageManager.getString("label.pairwise_aligned_sequences"),
             AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
   }
+
+  public long getTotal()
+  {
+    return total;
+  }
+
+  public long getProgress()
+  {
+    return progress;
+  }
 }
index d68d95f..8189313 100644 (file)
@@ -267,4 +267,17 @@ public class ProgressBar implements IProgressIndicator
     });
   }
 
+  /*
+   *
+   */
+  public JProgressBar getProgressBar(long id)
+  {
+    for (Component component : progressBars.get(id).getComponents())
+    { 
+      if (component.getClass().equals(JProgressBar.class))
+       return (JProgressBar) component;
+    }
+    return null;
+  }
+
 }
index ef0b2aa..042f178 100755 (executable)
@@ -119,10 +119,10 @@ public class RotatableCanvas extends JPanel
   private Point[] axisEndPoints;
 
   // fields for 'select rectangle' (JAL-1124)
-  // int rectx1;
-  // int recty1;
-  // int rectx2;
-  // int recty2;
+   int rectx1;
+   int recty1;
+   int rectx2;
+   int recty2;
 
   AlignmentViewport av;
 
@@ -405,11 +405,11 @@ public class RotatableCanvas extends JPanel
       }
     }
     // //Now the rectangle
-    // if (rectx2 != -1 && recty2 != -1) {
-    // g.setColor(Color.white);
-    //
-    // g.drawRect(rectx1,recty1,rectx2-rectx1,recty2-recty1);
-    // }
+     if (rectx2 != -1 && recty2 != -1) {
+     g.setColor(Color.white);
+    
+     g.drawRect(rectx1,recty1,rectx2-rectx1,recty2-recty1);
+     }
   }
 
   /**
@@ -511,10 +511,10 @@ public class RotatableCanvas extends JPanel
       // Cache.warn("DEBUG: Rectangle selection");
       // todo not yet enabled as rectx2, recty2 are always -1
       // need to set them in mouseDragged; JAL-1124
-      // if ((rectx2 != -1) && (recty2 != -1))
-      // {
-      // rectSelect(rectx1, recty1, rectx2, recty2);
-      // }
+       if ((rectx2 != -1) && (recty2 != -1))
+       {
+       rectSelect(rectx1, recty1, rectx2, recty2);
+       }
     }
 
     repaint();
@@ -564,10 +564,10 @@ public class RotatableCanvas extends JPanel
     mouseX = x;
     mouseY = y;
 
-    // rectx1 = x;
-    // recty1 = y;
-    // rectx2 = -1;
-    // recty2 = -1;
+     rectx1 = x;
+     recty1 = y;
+     rectx2 = -1;
+     recty2 = -1;
 
     SequenceI found = findSequenceAtPoint(x, y);
 
@@ -632,8 +632,8 @@ public class RotatableCanvas extends JPanel
     // Check if this is a rectangle drawing drag
     if ((evt.getModifiersEx() & InputEvent.BUTTON2_DOWN_MASK) != 0)
     {
-      // rectx2 = evt.getX();
-      // recty2 = evt.getY();
+       rectx2 = evt.getX();
+       recty2 = evt.getY();
     }
     else
     {
@@ -749,15 +749,25 @@ public class RotatableCanvas extends JPanel
     {
       SequencePoint sp = sequencePoints.get(i);
       int tmp1 = (int) (((sp.coord.x - centre[0]) * getScaleFactor())
+              * (getWidth() / 3.15)
               + (getWidth() / 2.0));
+      float pre1 = ((sp.coord.x - centre[0]) * getScaleFactor());
       int tmp2 = (int) (((sp.coord.y - centre[1]) * getScaleFactor())
+              * (getHeight() / 1.70)
               + (getHeight() / 2.0));
+      float pre2 = ((sp.coord.y - centre[1]) * getScaleFactor());
 
       if ((tmp1 > x1) && (tmp1 < x2) && (tmp2 > y1) && (tmp2 < y2))
       {
         if (av != null)
         {
           SequenceI sequence = sp.getSequence();
+          if (av.getSelectionGroup() == null)
+          {
+            SequenceGroup sg = new SequenceGroup();
+            sg.setEndRes(av.getAlignment().getWidth() - 1);
+            av.setSelectionGroup(sg);
+          }
           if (!av.getSelectionGroup().getSequences(null).contains(sequence))
           {
             av.getSelectionGroup().addSequence(sequence, true);
index 0e834d4..9658a92 100644 (file)
@@ -40,6 +40,7 @@ import javax.swing.JComboBox;
 import javax.swing.JLabel;
 import javax.swing.JMenuItem;
 import javax.swing.JPopupMenu;
+import javax.swing.JProgressBar;
 import javax.swing.JTable;
 import javax.swing.SwingUtilities;
 import javax.swing.table.AbstractTableModel;
@@ -1892,4 +1893,10 @@ public class StructureChooser extends GStructureChooser
     }
 
   }
+
+  @Override
+  public JProgressBar getProgressBar(long id)
+  {
+    return progressBar.getProgressBar(id);
+  }
 }
index 961caa7..e464b2b 100644 (file)
@@ -38,6 +38,7 @@ import javax.swing.JComponent;
 import javax.swing.JEditorPane;
 import javax.swing.JInternalFrame;
 import javax.swing.JPanel;
+import javax.swing.JProgressBar;
 import javax.swing.JScrollPane;
 import javax.swing.JTabbedPane;
 import javax.swing.JTextArea;
@@ -945,4 +946,10 @@ public class WebserviceInfo extends GWebserviceInfo
   {
     return progressBar.operationInProgress();
   }
+
+  @Override
+  public JProgressBar getProgressBar(long id)
+  {
+    return progressBar.getProgressBar(id);
+  }
 }
index 3cd52ae..5daea78 100755 (executable)
@@ -60,6 +60,8 @@ public class GCutAndPasteTransfer extends JInternalFrame
 
   JMenuItem pasteMenu = new JMenuItem();
 
+  JMenuItem fontSizeMenu = new JMenuItem();
+
   BorderLayout borderLayout2 = new BorderLayout();
 
   protected JPanel inputButtonPanel = new JPanel();
@@ -190,6 +192,15 @@ public class GCutAndPasteTransfer extends JInternalFrame
         pasteMenu_actionPerformed(e);
       }
     });
+    fontSizeMenu.setText(MessageManager.getString("action.change_font"));
+    fontSizeMenu.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+       fontSizeMenu_actionPerformed(e);
+      }
+    });
     copyItem.setText(MessageManager.getString("action.copy"));
     copyItem.addActionListener(new ActionListener()
     {
@@ -205,6 +216,7 @@ public class GCutAndPasteTransfer extends JInternalFrame
     editMenu.add(selectAll);
     editMenu.add(copyItem);
     editMenu.add(pasteMenu);
+    editMenu.add(fontSizeMenu);
     this.getContentPane().add(scrollPane, java.awt.BorderLayout.CENTER);
     inputButtonPanel.add(ok);
     inputButtonPanel.add(cancel);
@@ -243,6 +255,14 @@ public class GCutAndPasteTransfer extends JInternalFrame
   }
 
   /**
+   * shows a menu for changing the font
+   * @param e
+   */
+  public void fontSizeMenu_actionPerformed(ActionEvent e)
+  {
+  }
+
+  /**
    * DOCUMENT ME!
    * 
    * @param e
index cf7bc4e..ec5b209 100755 (executable)
@@ -85,6 +85,23 @@ public class GPCAPanel extends JInternalFrame
       zCombobox.addItem("dim " + i);
     }
   }
+  public GPCAPanel(int dim)
+  {
+    try
+    {
+      jbInit();
+    } catch (Exception e)
+    {
+      e.printStackTrace();
+    }
+
+    for (int i = 1; i <= dim; i++)
+    {
+      xCombobox.addItem("dim " + i);
+      yCombobox.addItem("dim " + i);
+      zCombobox.addItem("dim " + i);
+    }
+  }
 
   private void jbInit() throws Exception
   {
diff --git a/src/jalview/jbgui/GPaSiMapPanel.java b/src/jalview/jbgui/GPaSiMapPanel.java
new file mode 100755 (executable)
index 0000000..369a005
--- /dev/null
@@ -0,0 +1,369 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.jbgui;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.JButton;
+import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JComboBox;
+import javax.swing.JInternalFrame;
+import javax.swing.JLabel;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JPanel;
+import javax.swing.event.MenuEvent;
+import javax.swing.event.MenuListener;
+
+import jalview.util.ImageMaker.TYPE;
+import jalview.util.MessageManager;
+
+public class GPaSiMapPanel extends JInternalFrame
+{
+  private static final Font VERDANA_12 = new Font("Verdana", 0, 12);
+
+  protected JComboBox<String> xCombobox = new JComboBox<>();
+
+  protected JComboBox<String> yCombobox = new JComboBox<>();
+
+  protected JComboBox<String> zCombobox = new JComboBox<>();
+
+  protected JMenu viewMenu = new JMenu();
+
+  protected JCheckBoxMenuItem showLabels = new JCheckBoxMenuItem();
+
+  protected JMenu associateViewsMenu = new JMenu();
+
+  protected JLabel statusBar = new JLabel();
+
+  protected JPanel statusPanel = new JPanel();
+
+  protected JMenuItem originalSeqData;
+
+  /**
+   * Constructor
+   */
+  public GPaSiMapPanel()
+  {
+    try
+    {
+      jbInit();
+    } catch (Exception e)
+    {
+      e.printStackTrace();
+    }
+
+    for (int i = 1; i < 8; i++)
+    {
+      xCombobox.addItem("dim " + i);
+      yCombobox.addItem("dim " + i);
+      zCombobox.addItem("dim " + i);
+    }
+  }
+  public GPaSiMapPanel(int dim)
+  {
+    try
+    {
+      jbInit();
+    } catch (Exception e)
+    {
+      e.printStackTrace();
+    }
+
+    for (int i = 1; i <= dim; i++)
+    {
+      xCombobox.addItem("dim " + i);
+      yCombobox.addItem("dim " + i);
+      zCombobox.addItem("dim " + i);
+    }
+  }
+
+  private void jbInit() throws Exception
+  {
+    setFrameIcon(null);
+    setName("jalview-pca");
+    this.getContentPane().setLayout(new BorderLayout());
+    JPanel jPanel2 = new JPanel();
+    jPanel2.setLayout(new FlowLayout());
+    JLabel jLabel1 = new JLabel();
+    jLabel1.setFont(VERDANA_12);
+    jLabel1.setText("x=");
+    JLabel jLabel2 = new JLabel();
+    jLabel2.setFont(VERDANA_12);
+    jLabel2.setText("y=");
+    JLabel jLabel3 = new JLabel();
+    jLabel3.setFont(VERDANA_12);
+    jLabel3.setText("z=");
+    jPanel2.setBackground(Color.white);
+    jPanel2.setBorder(null);
+    zCombobox.setFont(VERDANA_12);
+    zCombobox.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        doDimensionChange();
+      }
+    });
+    yCombobox.setFont(VERDANA_12);
+    yCombobox.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        doDimensionChange();
+      }
+    });
+    xCombobox.setFont(VERDANA_12);
+    xCombobox.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        doDimensionChange();
+      }
+    });
+    JButton resetButton = new JButton();
+    resetButton.setFont(VERDANA_12);
+    resetButton.setText(MessageManager.getString("action.reset"));
+    resetButton.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        resetButton_actionPerformed();
+      }
+    });
+    JMenu fileMenu = new JMenu();
+    fileMenu.setText(MessageManager.getString("action.file"));
+    JMenu saveMenu = new JMenu();
+    saveMenu.setText(MessageManager.getString("action.save_as"));
+    JMenuItem eps = new JMenuItem("EPS");
+    eps.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        makePCAImage(TYPE.EPS);
+      }
+    });
+    JMenuItem png = new JMenuItem("PNG");
+    png.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        makePCAImage(TYPE.PNG);
+      }
+    });
+    JMenuItem outputValues = new JMenuItem();
+    outputValues.setText(MessageManager.getString("label.output_values"));
+    outputValues.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        outputValues_actionPerformed();
+      }
+    });
+    JMenuItem outputPoints = new JMenuItem();
+    outputPoints.setText(MessageManager.getString("label.output_points"));
+    outputPoints.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        outputPoints_actionPerformed();
+      }
+    });
+    JMenuItem outputProjPoints = new JMenuItem();
+    outputProjPoints.setText(
+            MessageManager.getString("label.output_transformed_points"));
+    outputProjPoints.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        outputProjPoints_actionPerformed();
+      }
+    });
+    JMenuItem print = new JMenuItem();
+    print.setText(MessageManager.getString("action.print"));
+    print.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        print_actionPerformed();
+      }
+    });
+    JMenuItem outputAlignment = new JMenuItem();
+    outputAlignment.setText(
+      MessageManager.getString("label.output_alignment"));
+    outputAlignment.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        outputAlignment_actionPerformed();
+      }
+    });
+    viewMenu.setText(MessageManager.getString("action.view"));
+    viewMenu.addMenuListener(new MenuListener()
+    {
+      @Override
+      public void menuSelected(MenuEvent e)
+      {
+        viewMenu_menuSelected();
+      }
+
+      @Override
+      public void menuDeselected(MenuEvent e)
+      {
+      }
+
+      @Override
+      public void menuCanceled(MenuEvent e)
+      {
+      }
+    });
+    showLabels.setText(MessageManager.getString("label.show_labels"));
+    showLabels.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        showLabels_actionPerformed();
+      }
+    });
+    JMenuItem bgcolour = new JMenuItem();
+    bgcolour.setText(MessageManager.getString("action.background_colour"));
+    bgcolour.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        bgcolour_actionPerformed();
+      }
+    });
+    originalSeqData = new JMenuItem();
+    originalSeqData.setText(MessageManager.getString("label.input_data"));
+    originalSeqData.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        originalSeqData_actionPerformed();
+      }
+    });
+    associateViewsMenu.setText(
+            MessageManager.getString("label.associate_nodes_with"));
+
+    statusPanel.setLayout(new GridLayout());
+    statusBar.setFont(VERDANA_12);
+    // statusPanel.setBackground(Color.lightGray);
+    // statusBar.setBackground(Color.lightGray);
+    // statusPanel.add(statusBar, null);
+    JPanel panelBar = new JPanel(new BorderLayout());
+    panelBar.add(jPanel2, BorderLayout.NORTH);
+    panelBar.add(statusPanel, BorderLayout.SOUTH);
+    this.getContentPane().add(panelBar, BorderLayout.SOUTH);
+    jPanel2.add(jLabel1, null);
+    jPanel2.add(xCombobox, null);
+    jPanel2.add(jLabel2, null);
+    jPanel2.add(yCombobox, null);
+    jPanel2.add(jLabel3, null);
+    jPanel2.add(zCombobox, null);
+    jPanel2.add(resetButton, null);
+
+    JMenuBar jMenuBar1 = new JMenuBar();
+    jMenuBar1.add(fileMenu);
+    jMenuBar1.add(viewMenu);
+    setJMenuBar(jMenuBar1);
+    fileMenu.add(saveMenu);
+    fileMenu.add(outputValues);
+    fileMenu.add(print);
+    fileMenu.add(originalSeqData);
+    fileMenu.add(outputPoints);
+    fileMenu.add(outputProjPoints);
+    fileMenu.add(outputAlignment);
+    saveMenu.add(eps);
+    saveMenu.add(png);
+    viewMenu.add(showLabels);
+    viewMenu.add(bgcolour);
+    viewMenu.add(associateViewsMenu);
+  }
+
+  protected void resetButton_actionPerformed()
+  {
+  }
+
+  protected void outputPoints_actionPerformed()
+  {
+  }
+
+  protected void outputProjPoints_actionPerformed()
+  {
+  }
+
+  protected void outputAlignment_actionPerformed()
+  {
+  }
+
+  public void makePCAImage(TYPE imageType)
+  {
+  }
+
+  protected void outputValues_actionPerformed()
+  {
+  }
+
+  protected void print_actionPerformed()
+  {
+  }
+
+  protected void showLabels_actionPerformed()
+  {
+  }
+
+  protected void bgcolour_actionPerformed()
+  {
+  }
+
+  protected void originalSeqData_actionPerformed()
+  {
+  }
+
+  protected void viewMenu_menuSelected()
+  {
+  }
+
+  protected void doDimensionChange()
+  {
+  }
+}
index ad91464..85d0db3 100755 (executable)
@@ -24,6 +24,7 @@ import jalview.util.Format;
 import jalview.util.MessageManager;
 
 import java.io.PrintStream;
+import java.lang.Math;
 import java.util.Arrays;
 
 /**
@@ -107,6 +108,32 @@ public class Matrix implements MatrixI
     }
   }
 
+  public Matrix(float[][] values)
+  {
+    this.rows = values.length;
+    this.cols = this.rows == 0 ? 0 : values[0].length;
+
+    /*
+     * make a copy of the values array, for immutability
+     */
+    this.value = new double[rows][];
+    int i = 0;
+    for (float[] row : values)
+    {
+      if (row != null)
+      {
+        value[i] = new double[row.length];
+       int j = 0;
+       for (float oldValue : row)
+       {
+         value[i][j] = oldValue;
+         j++;
+       }
+      }
+      i++;
+    }
+  }
+
   @Override
   public MatrixI transpose()
   {
@@ -164,7 +191,10 @@ public class Matrix implements MatrixI
          */
         for (int k = 0; k < in.width(); k++)
         {
-          tmp[i][j] += (in.getValue(i, k) * this.value[k][j]);
+         if (!Double.isNaN(in.getValue(i,k)) && !Double.isNaN(this.value[k][j]))
+         {
+            tmp[i][j] += (in.getValue(i, k) * this.value[k][j]);
+         }
         }
       }
     }
@@ -803,12 +833,24 @@ public class Matrix implements MatrixI
   }
 
   /**
+  * returns the matrix as a double[][] array
+  *
+  * @return
+  */
+  @Override
+  public double[][] asArray()
+  {
+    return value;
+  }
+
+  /**
    * Returns an array containing the values in the specified column
    * 
    * @param col
    * 
    * @return
    */
+  @Override
   public double[] getColumn(int col)
   {
     double[] out = new double[rows];
@@ -831,7 +873,7 @@ public class Matrix implements MatrixI
   @Override
   public void printD(PrintStream ps, String format)
   {
-    for (int j = 0; j < rows; j++)
+    for (int j = 0; j < d.length; j++)
     {
       Format.print(ps, format, d[j]);
     }
@@ -973,6 +1015,26 @@ public class Matrix implements MatrixI
     }
   }
 
+  /**
+   * Add d to all entries of this matrix
+   * 
+   * @param d ~ value to add
+   */
+  @Override
+  public void add(double d)
+  {
+    for (double[] row : value)
+    {
+      if (row != null)
+      {
+        for (int i = 0; i < row.length; i++)
+        {
+          row[i] += d;
+        }
+      }
+    }
+  }
+
   @Override
   public void setD(double[] v)
   {
@@ -1022,4 +1084,354 @@ public class Matrix implements MatrixI
     }
     return true;
   }
+
+  /**
+   * Returns a copy in which  every value in the matrix is its absolute
+   * 
+   * @return
+   */
+  @Override
+  public MatrixI absolute()
+  {
+    MatrixI copy = this.copy();
+    for (int i = 0; i < copy.width(); i++)
+    {
+      double[] row = copy.getRow(i);
+      if (row != null)
+      {
+        for (int j = 0; j < row.length; j++)
+        {
+          row[j] = Math.abs(row[j]);
+        }
+      }
+    }
+    return copy;
+  }
+
+  /**
+   * Returns the mean of each row
+   * 
+   * @return
+   */
+  @Override
+  public double[] meanRow()
+  {
+    double[] mean = new double[rows];
+    int i = 0;
+    for (double[] row : value)
+    {
+      if (row != null)
+      {
+       mean[i++] = MiscMath.mean(row);
+      }
+    }
+    return mean;
+  }
+
+  /**
+   * Returns the mean of each column
+   * 
+   * @return
+   */
+  @Override
+  public double[] meanCol()
+  {
+    double[] mean = new double[cols];
+    for (int j = 0; j < cols; j++)
+    {
+      double[] column = getColumn(j);
+      if (column != null)
+      {
+       mean[j] = MiscMath.mean(column);
+      }
+    }
+    return mean;
+  }
+
+  /**
+  * return a flattened matrix containing the sum of each column
+  *
+  * @return
+  */
+  @Override
+  public double[] sumCol()
+  {
+    double[] sum = new double[cols];
+    for (int j = 0; j < cols; j++)
+    {
+      double[] column = getColumn(j);
+      if (column != null)
+      {
+       sum[j] = MiscMath.sum(column);
+      }
+    } 
+    return sum;
+  }
+
+  /**
+  * returns the mean value of the complete matrix
+  *
+  * @return
+  */
+  @Override
+  public double mean()
+  {
+    double sum = 0;
+    int nanCount = 0;
+    for (double[] row : value)
+    {
+      for (double col : row)
+      {
+       if (!Double.isNaN(col))
+       {
+         sum += col;
+       } else {
+         nanCount++;
+       }
+      }
+    }
+    return sum / (double) (this.rows * this.cols - nanCount);
+  }
+
+  /**
+  * fills up a diagonal matrix with its transposed copy
+  * !other side should be filled with 0
+  * !keeps Double.NaN found in either side
+  *
+  * TODO check on which side it was diagonal and only do calculations for the other side
+  */
+  @Override
+  public void fillDiagonal()
+  {
+    int n = this.rows;
+    int m = this.cols;
+    MatrixI copy = this.transpose();   // goes through each element in the matrix and
+    for (int i = 0; i < n; i++)                // adds the value in the transposed copy to the original value
+    {
+      for (int j = 0; j < m; j++)
+      {
+       if (i != j)
+       {
+         this.addValue(i, j, copy.getValue(i,j));
+       }
+      }
+    }
+  }
+
+  /**
+  * counts the number of Double.NaN in the matrix
+  *
+  * @return
+  */
+  @Override
+  public int countNaN()
+  {
+    int NaN = 0;
+    for (int i = 0; i < this.rows; i++)
+    {
+      for (int j = 0; j < this.cols; j++)
+      {
+       if (Double.isNaN(this.getValue(i,j)))
+       {
+         NaN++;
+       }
+      }
+    }
+    return NaN;
+  }
+
+  /**
+  * performs an element-wise addition of this matrix by another matrix ~ this - m
+  * @param m ~ other matrix
+  *
+  * @return
+  */
+  @Override
+  public MatrixI add(MatrixI m)
+  {
+    if (m.width() != cols || m.height() != rows)
+    {
+      throw new IllegalArgumentException("Can't add a " + m.height() + "x" + m.width() + " to a " + this.rows + "x" + this.cols + " matrix");
+    }
+    double[][] tmp = new double[this.rows][this.cols];
+    for (int i = 0; i < this.rows; i++)
+    {
+      for (int j = 0; j < this.cols; j++)
+      {
+       tmp[i][j] = this.getValue(i,j) + m.getValue(i,j);
+      }
+    }
+    return new Matrix(tmp);
+  }
+
+  /**
+  * performs an element-wise subtraction of this matrix by another matrix ~ this - m
+  * @param m ~ other matrix
+  *
+  * @return
+  */
+  @Override
+  public MatrixI subtract(MatrixI m)
+  {
+    if (m.width() != cols || m.height() != rows)
+    {
+      throw new IllegalArgumentException("Can't subtract a " + m.height() + "x" + m.width() + " from a " + this.rows + "x" + this.cols + " matrix");
+    }
+    double[][] tmp = new double[this.rows][this.cols];
+    for (int i = 0; i < this.rows; i++)
+    {
+      for (int j = 0; j < this.cols; j++)
+      {
+       tmp[i][j] = this.getValue(i,j) -  m.getValue(i,j);
+      }
+    }
+    return new Matrix(tmp);
+  }
+
+  /**
+  * performs an element-wise multiplication of this matrix by another matrix ~ this * m
+  * @param m ~ other matrix
+  *
+  * @return
+  */
+  @Override
+  public MatrixI elementwiseMultiply(MatrixI m)
+  {
+    if (m.width() != cols || m.height() != rows)
+    {
+      throw new IllegalArgumentException("Can't multiply a " + this.rows + "x" + this.cols + " by a " + m.height() + "x" + m.width() + " matrix");
+    }
+    double[][] tmp = new double[this.rows][this.cols];
+    for (int i = 0; i < this.rows; i++)
+    {
+      for (int j = 0; j < this.cols; j++)
+      {
+        tmp[i][j] = this.getValue(i, j) * m.getValue(i,j);
+      }
+    }
+    return new Matrix(tmp);
+  }
+
+  /**
+  * performs an element-wise division of this matrix by another matrix ~ this / m
+  * @param m ~ other matrix
+  *
+  * @return
+  */
+  @Override
+  public MatrixI elementwiseDivide(MatrixI m)
+  {
+    if (m.width() != cols || m.height() != rows)
+    {
+      throw new IllegalArgumentException("Can't divide a " + this.rows + "x" + this.cols + " by a " + m.height() + "x" + m.width() + " matrix");
+    }
+    double[][] tmp = new double[this.rows][this.cols];
+    for (int i = 0; i < this.rows; i++)
+    {
+      for (int j = 0; j < this.cols; j++)
+      {
+        tmp[i][j] = this.getValue(i, j) / m.getValue(i,j);
+      }
+    }
+    return new Matrix(tmp);
+  }
+
+  /**
+  * calculate the root-mean-square for tow matrices
+  * @param m ~ other matrix
+  *
+  * @return
+  */
+  @Override
+  public double rmsd(MatrixI m)
+  {
+    MatrixI squaredDeviates = this.subtract(m);
+    squaredDeviates = squaredDeviates.preMultiply(squaredDeviates);
+    return Math.sqrt(squaredDeviates.mean());
+  }
+
+  /**
+  * calculates the Frobenius norm of this matrix
+  *
+  * @return
+  */
+  @Override
+  public double norm()
+  {
+    double result = 0;
+    for (double[] row : value)
+    {
+      for (double val : row)
+      {
+       result += Math.pow(val, 2);
+      }
+    }
+    return Math.sqrt(result);
+  }
+
+  /**
+  * returns the sum of all values in this matrix
+  *
+  * @return
+  */
+  @Override
+  public double sum()
+  {
+    double sum = 0;
+    for (double[] row : value)
+    {
+      for (double val : row)
+      {
+       sum += (Double.isNaN(val)) ? 0.0 : val;
+      }
+    }
+    return sum;
+  }
+
+  /**
+  * returns the sum-product of this matrix with vector v
+  * @param v ~ vector
+  *
+  * @return
+  */
+  @Override
+  public double[] sumProduct(double[] v)
+  {
+    if (v.length != cols)
+    {
+      throw new IllegalArgumentException("Vector and matrix do not have the same dimension! (" + v.length + " != " + cols + ")");
+    }
+    double[] result = new double[rows];
+    for (int i = 0; i < rows; i++)
+    {
+      double[] row = value[i];
+      double sum = 0;
+      for (int j = 0; j < row.length; j++)
+      {
+       sum += row[j] * v[j];
+      }
+      result[i] = sum;
+    }
+    return result;
+  }
+
+  /**
+  * mirrors columns of the matrix
+  *
+  * @return
+  */
+  @Override
+  public MatrixI mirrorCol()
+  {
+    double[][] result = new double[rows][cols];
+    for (int i = 0; i < rows; i++)
+    {
+      int k = cols - 1;        // reverse col
+      for (int j = 0; j < cols; j++)
+      {
+       result[i][k--] = this.getValue(i,j);
+      }
+    }
+    return new Matrix(result);
+  }
 }
index e98007e..7faac73 100644 (file)
@@ -61,6 +61,13 @@ public interface MatrixI
   void setValue(int i, int j, double d);
 
   /**
+  * Returns the matrix as a double[][] array
+  *
+  * @return
+  */
+  double[][] asArray();
+
+  /**
    * Answers a copy of the values in the i'th row
    * 
    * @return
@@ -68,6 +75,13 @@ public interface MatrixI
   double[] getRow(int i);
 
   /**
+   * Answers a copy of the values in the i'th column
+   * 
+   * @return
+   */
+  double[] getColumn(int i);
+
+  /**
    * Answers a new matrix with a copy of the values in this one
    * 
    * @return
@@ -161,6 +175,13 @@ public interface MatrixI
   void multiply(double d);
 
   /**
+  * Add d to all entries of this matrix
+  *
+  * @param d ~ value to add
+  */
+  void add(double d);
+
+  /**
    * Answers true if the two matrices have the same dimensions, and
    * corresponding values all differ by no more than delta (which should be a
    * positive value), else false
@@ -170,4 +191,127 @@ public interface MatrixI
    * @return
    */
   boolean equals(MatrixI m2, double delta);
+
+  /**
+   * Returns a copy in which  every value in the matrix is its absolute
+   */
+  MatrixI absolute();
+
+  /**
+   * Returns the mean of each row
+   */
+  double[] meanRow();
+
+  /**
+   * Returns the mean of each column
+   */
+  double[] meanCol();
+
+  /**
+  * Returns a flattened matrix containing the sum of each column
+  *
+  * @return
+  */
+  double[] sumCol();
+
+  /**
+  * returns the mean value of the complete matrix
+  */
+  double mean();
+
+  /**
+  * fills up a diagonal matrix with its transposed copy
+  * !other side should be filled with either 0 or Double.NaN
+  */
+  void fillDiagonal();
+
+  /**
+  * counts the number of Double.NaN in the matrix
+  *
+  * @return
+  */
+  int countNaN();
+
+  /**
+  * performs an element-wise addition of this matrix by another matrix
+  * !matrices have to be the same size
+  * @param m ~ other matrix
+  * 
+  * @return
+  * @throws IllegalArgumentException
+  *           if this and m do not have the same dimensions
+  */
+  MatrixI add(MatrixI m);
+
+  /**
+  * performs an element-wise subtraction of this matrix by another matrix
+  * !matrices have to be the same size
+  * @param m ~ other matrix
+  * 
+  * @return
+  * @throws IllegalArgumentException
+  *           if this and m do not have the same dimensions
+  */
+  MatrixI subtract(MatrixI m);
+  /**
+  * performs an element-wise multiplication of this matrix by another matrix ~ this * m
+  * !matrices have to be the same size
+  * @param m ~ other matrix
+  *
+  * @return
+  * @throws IllegalArgumentException
+  *    if this and m do not have the same dimensions
+  */
+  MatrixI elementwiseMultiply(MatrixI m);
+
+  /**
+  * performs an element-wise division of this matrix by another matrix ~ this / m
+  * !matrices have to be the same size
+  * @param m ~ other matrix
+  *
+  * @return
+  * @throws IllegalArgumentException
+  *    if this and m do not have the same dimensions
+  */
+  MatrixI elementwiseDivide(MatrixI m);
+
+  /**
+  * calculates the root-mean-square for two matrices
+  * @param m ~ other matrix
+  *  
+  * @return
+  */
+  double rmsd(MatrixI m);
+
+  /**
+  * calculates the Frobenius norm of this matrix
+  *
+  * @return
+  */
+  double norm();
+  
+  /**
+  * returns the sum of all values in this matrix
+  *
+  * @return
+  */
+  double sum();
+
+  /**
+  * returns the sum-product of this matrix with vector v
+  * @param v ~ vector
+  *
+  * @return
+  * @throws IllegalArgumentException
+  *    if this.cols and v do not have the same length
+  */
+  double[] sumProduct(double[] v);
+
+  /**
+  * mirrors the columns of this matrix
+  *
+  * @return
+  */
+  MatrixI mirrorCol();
 }
diff --git a/src/jalview/math/MiscMath.java b/src/jalview/math/MiscMath.java
new file mode 100755 (executable)
index 0000000..b5218d5
--- /dev/null
@@ -0,0 +1,381 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.math;
+
+import jalview.util.Format;
+
+import java.lang.Math;
+import java.util.Arrays;
+
+/**
+ * A collection of miscellaneous mathematical operations
+ * @AUTHOR MorellThomas
+ */
+public class MiscMath
+{
+  /**
+  * prints an array
+  * @param m ~ array
+  */
+  public static void print(double[] m, String format)
+  {
+    System.out.print("[ ");
+    for (double a : m)
+    {
+      Format.print(System.out, format + " ", a);
+    }
+    System.out.println("]");
+  }
+
+  /**
+  * calculates the mean of an array 
+  *
+  * @param m ~ array
+  * @return
+  */
+  public static double mean(double[] m)
+  {
+    double sum = 0;
+    int nanCount = 0;
+    for (int i = 0; i < m.length; i++)
+    {
+      if (!Double.isNaN(m[i])) // ignore NaN values in the array
+      {
+        sum += m[i];
+      } else {
+       nanCount++;
+      }
+    }
+    return sum / (double) (m.length - nanCount);
+  }
+
+  /**
+  * calculates the sum of an array 
+  *
+  * @param m ~ array
+  * @return
+  */
+  public static double sum(double[] m)
+  {
+    double sum = 0;
+    for (int i = 0; i < m.length; i++)
+    {
+      if (!Double.isNaN(m[i])) // ignore NaN values in the array
+      {
+        sum += m[i];
+      }
+    }
+    return sum;
+  }
+
+  /**
+  * calculates the square root of each element in an array
+  *
+  * @param m ~ array
+  *
+  * @return
+  * TODO
+  * make general with function passed -> apply function to each element
+  */
+  public static double[] sqrt(double[] m)
+  {
+    double[] sqrts = new double[m.length];
+    for (int i = 0; i < m.length; i++)
+    {
+      sqrts[i] = Math.sqrt(m[i]);
+    }
+    return sqrts;
+  }
+
+  /**
+  * calculate element wise multiplication of two arrays with the same length
+  *
+  * @param a ~ array
+  * @param b ~ array
+  *
+  * @return
+  */
+  public static double[] elementwiseMultiply(byte[] a, double[] b) throws RuntimeException
+  {
+    if (a.length != b.length)  // throw exception if the arrays do not have the same length
+    {
+      throw new SameLengthException(a.length, b.length);
+    }
+    double[] result = new double[a.length];
+    for (int i = 0; i < a.length; i++)
+    {
+      result[i] = a[i] * b[i];
+    }
+    return result;
+  }
+  public static double[] elementwiseMultiply(double[] a, double[] b) throws RuntimeException
+  {
+    if (a.length != b.length)  // throw exception if the arrays do not have the same length
+    {
+      throw new SameLengthException(a.length, b.length);
+    }
+    double[] result = new double[a.length];
+    for (int i = 0; i < a.length; i++)
+    {
+      result[i] = a[i] * b[i];
+    }
+    return result;
+  }
+  public static byte[] elementwiseMultiply(byte[] a, byte[] b) throws RuntimeException
+  {
+    if (a.length != b.length)  // throw exception if the arrays do not have the same length
+    {
+      throw new SameLengthException(a.length, b.length);
+    }
+    byte[] result = new byte[a.length];
+    for (int i = 0; i < a.length; i++)
+    {
+      result[i] = (byte) (a[i] * b[i]);
+    }
+    return result;
+  }
+  public static double[] elementwiseMultiply(double[] a, double b)
+  {
+    double[] result = new double[a.length];
+    for (int i = 0; i < a.length; i++)
+    {
+      result[i] = a[i] * b;
+    }
+    return result;
+  }
+
+  /**
+  * calculate element wise division of two arrays ~ a / b
+  *
+  * @param a ~ array
+  * @param b ~ array
+  *
+  * @return
+  */
+  public static double[] elementwiseDivide(double[] a, double[] b) throws RuntimeException
+  {
+    if (a.length != b.length)  // throw exception if the arrays do not have the same length
+    {
+      throw new SameLengthException(a.length, b.length);
+    }
+    double[] result = new double[a.length];
+    for (int i = 0; i < a.length; i++)
+    {
+      result[i] = a[i] / b[i];
+    }
+    return result;
+  }
+
+  /**
+  * calculate element wise addition of two arrays
+  *
+  * @param a ~ array
+  * @param b ~ array
+  *
+  * @return
+  */
+  public static double[] elementwiseAdd(double[] a, double[] b) throws RuntimeException
+  {
+    if (a.length != b.length)  // throw exception if the arrays do not have the same length
+    {
+      throw new SameLengthException(a.length, b.length);
+    }
+    double[] result = new double[a.length];
+
+    for (int i = 0; i < a.length; i++)
+    {
+      result[i] += a[i] + b[i];
+    }
+    return result;
+  }
+  public static double[] elementwiseAdd(double[] a, double b)
+  {
+    double[] result = new double[a.length];
+    for (int i = 0; i < a.length; i++)
+    {
+      result[i] = a[i] + b;
+    }
+    return result;
+  }
+
+  /**
+  * returns true if two arrays are element wise within a tolerance
+  *
+  * @param a ~ array
+  * @param b ~ array
+  * @param rtol ~ relative tolerance
+  * @param atol ~ absolute tolerance
+  * @param equalNAN ~ whether NaN at the same position return true
+  *
+  * @return
+  */
+  public static boolean allClose(double[] a, double[] b, double rtol, double atol, boolean equalNAN)
+  {
+    boolean areEqual = true;
+    for (int i = 0; i < a.length; i++)
+    {
+      if (equalNAN && (Double.isNaN(a[i]) && Double.isNaN(b[i])))      // if equalNAN == true -> skip the NaN pair
+      {
+       continue;
+      }
+      if (Math.abs(a[i] - b[i]) > (atol + rtol * Math.abs(b[i])))      // check for the similarity condition -> if not met -> break and return false
+      {
+       areEqual = false;
+       break;
+      }
+    }
+    return areEqual;
+  }
+
+  /**
+  * returns the index of the maximum and the maximum value of an array
+  * 
+  * @param a ~ array
+  *
+  * @return
+  */
+  public static int[] findMax(int[] a)
+  {
+    int max = 0;
+    int maxIndex = 0;
+    for (int i = 0; i < a.length; i++)
+    {
+      if (a[i] > max)
+      {
+       max = a[i];
+       maxIndex = i;
+      }
+    }
+    return new int[]{maxIndex, max};
+  }
+
+  /**
+  * returns the dot product of two arrays
+  * @param a ~ array a
+  * @param b ~ array b
+  *
+  * @return
+  */
+  public static double dot(double[] a, double[] b)
+  {
+    if (a.length != b.length)
+    {
+      throw new IllegalArgumentException(String.format("Vectors do not have the same length (%d, %d)!", a.length, b.length));
+    }
+
+    double aibi = 0;
+    for (int i = 0; i < a.length; i++)
+    {
+      aibi += a[i] * b[i];
+    }
+    return aibi;
+  }
+
+  /**
+  * returns the euklidian norm of the vector
+  * @param v ~ vector
+  *
+  * @return
+  */
+  public static double norm(double[] v)
+  {
+    double result = 0;
+    for (double i : v)
+    {
+      result += Math.pow(i, 2);
+    }
+  return Math.sqrt(result);
+  }
+
+  /**
+  * returns the number of NaN in the vector
+  * @param v ~ vector
+  *
+  * @return
+  */
+  public static int countNaN(double[] v)
+  {
+    int cnt = 0;
+    for (double i : v)
+    {
+      if (Double.isNaN(i))
+      {
+       cnt++;
+      }
+    }
+    return cnt;
+  }
+
+  /**
+  * recursively calculates the permutations of total n items with r items per combination
+  * according to n!/(n-r)! by only multiplying the relevant terms
+  * @param n 
+  * @param r
+  *
+  * @return permutations
+  */
+  public static long permutations(int n, int r)
+  {
+    if (n < r)
+      return permutations(r, n);
+
+    long result = 1l;
+    for (int i = 0; i < r; i++)
+    {
+      result *= (n-i);
+    }
+    return result;
+  }
+
+  /**
+   * calculate all unique combinations of n elements into r sized groups
+   * @param n
+   * @param r
+   *
+   * @return
+   */
+  public static int combinations(int n, int r)
+  {
+    int result = 1;
+    for (int i = 0; i < r; i++)
+    {
+      result *= (n-1);
+    }
+    return (int) (result / MiscMath.factorial(r));
+  }
+
+  /**
+   * calculate the factorial of n (n >= 0)
+   * @param n
+   *
+   * @return
+   */
+  public static int factorial(int n)
+  {
+    int result = 1;
+    for (int i = 0; i < n; i++)
+    {
+      result *= (n - i);
+    }
+    return result;
+  }
+
+}
diff --git a/src/jalview/math/SameLengthException.java b/src/jalview/math/SameLengthException.java
new file mode 100644 (file)
index 0000000..9621827
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.math;
+
+public class SameLengthException extends RuntimeException
+{
+  public SameLengthException(int lengthA, int lengthB)
+  {
+    this("Your arrays do not have the same length!", lengthA, lengthB);
+  }
+
+  public SameLengthException(String message, int lengthA, int lengthB)
+  {
+    super(String.format("%s (%d and %d)", message, lengthA, lengthB));
+  }
+
+}
index 712c63b..4708888 100644 (file)
@@ -33,6 +33,7 @@ import java.util.List;
 import java.util.Map;
 
 import jalview.analysis.AnnotationSorter.SequenceAnnotationOrder;
+import jalview.analysis.Connectivity;
 import jalview.analysis.Conservation;
 import jalview.analysis.TreeModel;
 import jalview.api.AlignCalcManagerI;
@@ -3293,4 +3294,9 @@ public abstract class AlignmentViewport
             + savedUpToDate);
     return savedUpToDate;
   }
+
+  public Hashtable<SequenceI, Integer> calculateConnectivity(float[][] scores, byte dim)
+  {
+    return Connectivity.getConnectivity(this, scores, dim);
+  }
 }
index 1693294..71bef00 100644 (file)
@@ -101,7 +101,7 @@ public class PCAModel
     top = height - 1;
 
     points = new Vector<>();
-    Point[] scores = pca.getComponents(top - 1, top - 2, top - 3, 100);
+    Point[] scores = pca.getComponents(top - 1, top - 2, top - 3, 1);
 
     for (int i = 0; i < height; i++)
     {
@@ -153,7 +153,7 @@ public class PCAModel
   public void updateRcView(int dim1, int dim2, int dim3)
   {
     // note: actual indices for components are dim1-1, etc (patch for JAL-1123)
-    Point[] scores = pca.getComponents(dim1 - 1, dim2 - 1, dim3 - 1, 100);
+    Point[] scores = pca.getComponents(dim1 - 1, dim2 - 1, dim3 - 1, 1);
 
     for (int i = 0; i < pca.getHeight(); i++)
     {
diff --git a/src/jalview/viewmodel/PaSiMapModel.java b/src/jalview/viewmodel/PaSiMapModel.java
new file mode 100644 (file)
index 0000000..a0e5174
--- /dev/null
@@ -0,0 +1,261 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.viewmodel;
+
+import jalview.analysis.PaSiMap;
+import jalview.api.RotatableCanvasI;
+import jalview.api.analysis.ScoreModelI;
+import jalview.api.analysis.SimilarityParamsI;
+import jalview.datamodel.AlignmentView;
+import jalview.datamodel.Point;
+import jalview.datamodel.SequenceI;
+import jalview.datamodel.SequencePoint;
+import jalview.gui.PairwiseAlignPanel;
+import jalview.viewmodel.AlignmentViewport;
+
+import java.util.List;
+import java.util.Vector;
+
+public class PaSiMapModel
+{
+  /*
+   * inputs
+   */
+  private AlignmentViewport inputData;
+
+  private final SequenceI[] seqs;
+
+  /*
+   * options - score model, nucleotide / protein
+   */
+  private ScoreModelI scoreModel;
+
+  private boolean nucleotide = false;
+
+  /*
+   * outputs
+   */
+  private PaSiMap pasimap;
+
+  int top;
+
+  private List<SequencePoint> points;
+
+  /**
+   * Constructor given sequence data, score model and score calculation
+   * parameter options.
+   * 
+   * @param seqData
+   * @param sqs
+   * @param nuc
+   * @param modelName
+   * @param params
+   */
+  public PaSiMapModel(AlignmentViewport seqData, SequenceI[] sqs, boolean nuc,
+          ScoreModelI modelName)
+  {
+    inputData = seqData;
+    seqs = sqs;
+    nucleotide = nuc;
+    scoreModel = modelName;
+  }
+
+  /**
+   * Performs the PaSiMap calculation (in the same thread) and extracts result data
+   * needed for visualisation by PaSiMapPanel
+   */
+  public void calculate(PairwiseAlignPanel pap)
+  {
+    pasimap = new PaSiMap(inputData, scoreModel, pap);
+    pasimap.run(); // executes in same thread, wait for completion
+
+    // Now find the component coordinates
+    int ii = 0;
+
+    while ((ii < seqs.length) && (seqs[ii] != null))
+    {
+      ii++;
+    }
+
+    int width = pasimap.getWidth();
+    int height = pasimap.getHeight();
+    top = width;
+
+    points = new Vector<>();
+    Point[] scores = pasimap.getComponents(width - 1, width - 2, width - 3, 1);
+
+    for (int i = 0; i < height; i++)
+    {
+      SequencePoint sp = new SequencePoint(seqs[i], scores[i]);
+      points.add(sp);
+    }
+  }
+
+  public void updateRc(RotatableCanvasI rc)
+  {
+    rc.setPoints(points, pasimap.getHeight());
+  }
+
+  public boolean isNucleotide()
+  {
+    return nucleotide;
+  }
+
+  public void setNucleotide(boolean nucleotide)
+  {
+    this.nucleotide = nucleotide;
+  }
+
+  /**
+   * Answers the index of the principal dimension of the PaSiMap
+   * 
+   * @return
+   */
+  public int getTop()
+  {
+    return top;
+  }
+
+  public void setTop(int t)
+  {
+    top = t;
+  }
+
+  /**
+   * Updates the 3D coordinates for the list of points to the given dimensions.
+   * Principal dimension is getTop(). Next greatest eigenvector is getTop()-1.
+   * Note - pasimap.getComponents starts counting the spectrum from rank-2 to zero,
+   * rather than rank-1, so getComponents(dimN ...) == updateRcView(dimN+1 ..)
+   * 
+   * @param dim1
+   * @param dim2
+   * @param dim3
+   */
+  public void updateRcView(int dim1, int dim2, int dim3)
+  {
+    // note: actual indices for components are dim1-1, etc (patch for JAL-1123)
+    Point[] scores = pasimap.getComponents(dim1 - 1, dim2 - 1, dim3 - 1, 1);
+
+    for (int i = 0; i < pasimap.getHeight(); i++)
+    {
+      points.get(i).coord = scores[i];
+    }
+  }
+
+  public String getDetails()
+  {
+    return pasimap.getDetails();
+  }
+
+  public String getAlignmentOutput()
+  {
+    return pasimap.getAlignmentOutput();
+  }
+
+  public AlignmentViewport getInputData()
+  {
+    return inputData;
+  }
+
+  public void setInputData(AlignmentViewport data)
+  {
+    inputData = data;
+  }
+
+  public String getPointsasCsv(boolean transformed, int xdim, int ydim,
+          int zdim)
+  {
+    StringBuffer csv = new StringBuffer();
+    csv.append("\"Sequence\"");
+    if (transformed)
+    {
+      csv.append(",");
+      csv.append(xdim);
+      csv.append(",");
+      csv.append(ydim);
+      csv.append(",");
+      csv.append(zdim);
+    }
+    else
+    {
+      for (int d = 1, dmax = (int) pasimap.getDim(); d <= dmax; d++)
+      {
+        csv.append("," + d);
+      }
+    }
+    csv.append("\n");
+    for (int s = 0; s < seqs.length; s++)
+    {
+      csv.append("\"" + seqs[s].getName() + "\"");
+      if (!transformed)
+      {
+       double[] fl = pasimap.component(s);
+       for (int d = fl.length - 1; d >= 0; d--)
+       {
+         csv.append(",");
+         csv.append(fl[d]);
+       }
+      } else {
+        Point p = points.get(s).coord;
+        csv.append(",").append(p.x);
+        csv.append(",").append(p.y);
+        csv.append(",").append(p.z);
+      }
+      csv.append("\n");
+    }
+    return csv.toString();
+  }
+
+  public String getScoreModelName()
+  {
+    return scoreModel == null ? "" : scoreModel.getName();
+  }
+
+  public void setScoreModel(ScoreModelI sm)
+  {
+    this.scoreModel = sm;
+  }
+
+  public List<SequencePoint> getSequencePoints()
+  {
+    return points;
+  }
+
+  public void setSequencePoints(List<SequencePoint> sp)
+  {
+    points = sp;
+  }
+
+  /**
+   * Answers the object holding the values of the computed PaSiMap
+   * 
+   * @return
+   */
+  public PaSiMap getPasimapData()
+  {
+    return pasimap;
+  }
+
+  public void setPaSiMap(PaSiMap data)
+  {
+    pasimap = data;
+  }
+}
index a9a9730..03bdd0b 100644 (file)
@@ -76,4 +76,16 @@ public class AlignSeqTest
     String s = "aArRnNzZxX *.-?";
     assertArrayEquals(expected, as.indexEncode(s));
   }
+  @Test(groups= {"Functional"})
+  public void testGlobalAlignment()
+  {
+    String seq1="CAGCTAGCG",seq2="CCATACGA";
+    Sequence sq1=new Sequence("s1",seq1),sq2=new Sequence("s2",seq2);
+    // AlignSeq doesn't report the unaligned regions at either end of sequences
+    //String alseq1="-CAGCTAGCG-",alseq2="CCA--TA-CGA";
+    // so we check we have the aligned segment correct only
+    String alseq1="CAGCTAGCG",alseq2="CA--TA-CG";
+    AlignSeq as = AlignSeq.doGlobalNWAlignment(sq1,sq2,AlignSeq.DNA);
+    assertEquals(as.getAStr1()+"\n"+as.getAStr2(),alseq1+"\n"+alseq2);
+  }
 }