Cleanup JSON IO for constant-pH MD 04/4004/2
authorradakb <brian.radak@gmail.com>
Mon, 16 Apr 2018 18:28:32 +0000 (14:28 -0400)
committerDavid Hardy <dhardy@ks.uiuc.edu>
Mon, 16 Apr 2018 21:48:52 +0000 (16:48 -0500)
The writing of JSON files (constant pH restarts) is now much
cleaner. There are a few quirks of format that might preclude
using a more general writer, but this was easy enough of a change
without the extra testing that that would require.

Other than a lurking bug in constant-pH specific error reporting,
this outwardly changes nothing that a user will see. It will,
however, make mucking about in the restart format easier as time
goes on.

Change-Id: Ie62dd7cfa42f778078984a73a3e878ef222784a5

lib/namdcph/namdcph/json.tcl
lib/namdcph/namdcph/namdcph.core.tcl

index 837c021..ecc18fc 100644 (file)
@@ -247,28 +247,45 @@ proc json::_json2dict {{txtvar txt}} {
     }
 }
 
-proc json::dict2json {dictVal} {
-    # XXX: Currently this API isn't symmetrical, as to create proper
-    # XXX: JSON text requires type knowledge of the input data
-    set json ""
-
+# Convert a Tcl list to a JSON formatted list (i.e. with some notion of type).
+#
+# If ignoreFmt is true, just caste everything as a string.
+#
+proc json::dict2json {dictVal {ignoreFmt 0}} {
+    set FrmtdDict [list]
     dict for {key val} $dictVal {
-       # key must always be a string, val may be a number, string or
-       # bare word (true|false|null)
-       if {0 && ![string is double -strict $val]
-           && ![regexp {^(?:true|false|null)$} $val]} {
-           set val "\"$val\""
-       }
-       append json "\"$key\": $val," \n
+        if {[expr {![catch {dict size $val}]}]} {
+            lappend FrmtdDict "\"$key\":[dict2json $val $ignoreFmt]"
+        } else {
+            lappend FrmtdDict "\"$key\":[list2json $val $ignoreFmt]"
+        }
     }
-
-    return "\{${json}\}"
+    return "\{[join $FrmtdDict ,]\}"
 }
 
-proc json::list2json {listVal} {
-    return "\[[join $listVal ,]\]"
+# Convert a Tcl list to a JSON formatted list (i.e. with some notion of type).
+#
+# If ignoreFmt is true, just caste everything as a string.
+#
+proc json::list2json {listVal {ignoreFmt 0}} {
+    if {[llength $listVal] > 1} {
+        set FrmtdList [list]
+        foreach value $listVal {
+            lappend FrmtdList [list2json $value $ignoreFmt]
+        }
+        return "\[[join $FrmtdList ,]\]"
+    } else {
+        return [string2json $listVal $ignoreFmt]
+    }
 }
 
-proc json::string2json {str} {
+proc json::string2json {str {ignoreFmt 0}} {
+    if {$ignoreFmt} {
+        return "\"$str\""
+    }
+    if {[string is double -strict $str]} {
+        return $str
+    }
     return "\"$str\""
 }
+
index d0fb046..4a20e98 100644 (file)
@@ -766,58 +766,43 @@ proc ::namdcph::writeRestart {args} {
             return
         }
     }
-    namdFileBackup $restartFilename
-    set RestartFile [open $restartFilename "w"]
-    # TODO: This is god-awful. Is there not a useable json encoder that can do
-    # this for us with some guarantee of accuracy?
-    set cycleStr "\"cycle\":$cycle"
-    set pHStr "\"pH\":$SystempH"
 
-    set exclusionStr ""
+    # Here we write the restart as a dict in JSON format, however, this is NOT
+    # the same as converting a Tcl dict to JSON, because knowledge of types is
+    # lost in the conversion. The easiest (and maybe faster?) way is to build a
+    # list that contains all of the dict key/value pairs and then emulate the
+    # JSON format with commas and curly braces.
+    #
+    set rstrtList [list]
+    lappend rstrtList "\"cycle\":$cycle"
+    lappend rstrtList "\"pH\":$SystempH"
     if {[llength $excludeList]} {
-        set exclusionStr "\"exclude\":\["
-        foreach segresidname $excludeList {
-            set exclusionStr "$exclusionStr\"$segresidname\","
-        }
-        set exclusionStr "[string trimright $exclusionStr ","]\]"
-    }
-
-    set stateStr "\"states\":\["
-    foreach state [cphSystem get state] {
-        set stateStr "$stateStr\"$state\","
-    }
-    set stateStr "[string trimright $stateStr ","]\]"
-
-    set pKaiStr "\"pKais\":\["
-    foreach pKaiList [cphSystem get pKai] {
-        set pKaiStr "$pKaiStr\["
-        foreach pKai $pKaiList {
-            set pKaiStr "$pKaiStr$pKai,"
-        }
-        set pKaiStr "[string trimright $pKaiStr ","]\],"
+        lappend rstrtList "\"exclude\":[json::list2json $excludeList 1]"
     }
-    set pKaiStr "[string trimright $pKaiStr ","]\]"
-
-    set maxAttempts [cphTitrator get maxAttempts]
-    set MCStr "\"MCmoves\":\{\"maxProposalAttempts\":$maxAttempts,"
-
-    set defaultNumsteps [cphTitrator get numsteps default]
-    set defaultWeight [cphTitrator get weight default]
-    set MCStr "$MCStr\"default\":\{\"numsteps\":$defaultNumsteps,\"weight\":$defaultWeight\},"
-
+    lappend rstrtList "\"states\":[json::list2json [cphSystem get state] 1]"
+    lappend rstrtList "\"pKais\":[json::list2json [cphSystem get pKai]]"
+
+    set minMCDict [dict create]
+    dict set minMCDict MCmoves maxProposalAttempts\
+            [cphTitrator get maxAttempts]
+    dict set minMCDict MCmoves default numsteps\
+            [cphTitrator get numsteps default]
+    dict set minMCDict MCmoves default weight [cphTitrator get weight default]
     foreach moveLabel [cphTitrator get moveLabels] {
         set thisMoveInfo [cphTitrator get nodefaults $moveLabel]
         if {![dict size $thisMoveInfo]} continue
-        set MCStr "$MCStr\"$moveLabel\":\{"
-        dict for {key value} $thisMoveInfo {
-            set MCStr "$MCStr\"$key\":$value,"
-        }
-        set MCStr "[string trimright $MCStr ","]\},"
+        dict set minMCDict MCmoves $moveLabel $thisMoveInfo
     }
-    set MCStr "[string trimright $MCStr ","]\}"
+    # Note that dict2json returns curly braces on either end which need to be
+    # discarded since we are hacking the format into another dict.
+    lappend rstrtList [string range [json::dict2json $minMCDict] 1 end-1] 
 
-    puts $RestartFile "\{$cycleStr,$pHStr,$exclusionStr,$stateStr,$pKaiStr,$MCStr\}"
+    # Write everything to file.
+    namdFileBackup $restartFilename
+    set RestartFile [open $restartFilename "w"]
+    puts $RestartFile "\{[join $rstrtList ,]\}"
     close $RestartFile
+
     # Always checkpoint the PSF and PDB when a restart is written.
     file copy -force [mdFilename psf] "[outputName].psf"
     file copy -force [mdFilename pdb] "[outputName].pdb"
@@ -897,7 +882,7 @@ proc ::namdcph::cphWarn {msg} {
     print "$::namdcph::TITLE WARNING! $msg"
 }
 
-proc ::namdcph::cphAbort {msg} {
+proc ::namdcph::cphAbort {{msg ""}} {
     abort "$::namdcph::TITLE $msg"
 }