trinitycrmod 0.8.11 → 0.8.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6f1483d6a2c54bf25934cd21ab4ea09aac04046d
4
- data.tar.gz: e918fe7626ebc736ec38efbf0bc3d8bd29e99925
3
+ metadata.gz: 131387844abc1c45e607b655b3900fedd421693b
4
+ data.tar.gz: 8f046112c340599db8ae5a239781abff92a91172
5
5
  SHA512:
6
- metadata.gz: 78d81fb7310a3b62d9d2edb610b09f0289de4b8461dc70fbd0921fe299f4960c4443ae1a9fd527a256b855afd9cdbbf0dfd5a90106a4bbb2fe07ff9fbd7648d1
7
- data.tar.gz: 96bbc8c46ace6eecad55b20fc514f6c71ff24c90ffd352644a21c130a2e9929b38018cc832820b66a5e473d8e9be10cf4fc6a3d18f9bf1961ca8c156cf691086
6
+ metadata.gz: c708dd022fc82589bc6021819d31c474a39fabfacfe66136ad1e835d01c23081727caa8d6c8093e5c200ce32551b11d70396e941c51a03b1f6db74daeadd6b8a
7
+ data.tar.gz: 8c5c2bb2d46ea8fe18f9ad4704dd46e3ea1dea5d69fb7946122c6f4357393ce26cceccc7ec006d19516eb7c9c0145aa641ed63f08ab870d728af09bb891d24f6
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.8.11
1
+ 0.8.12
@@ -579,7 +579,38 @@
579
579
  :explanation=>
580
580
  "This variable must be a floating point number (an integer is also acceptable: it will be converted into a floating point number)."}],
581
581
  :type=>:Float,
582
- :autoscanned_defaults=>[-1.0]}}},
582
+ :autoscanned_defaults=>[-1.0]},
583
+ :flux_reset=>
584
+ {:should_include=>"true",
585
+ :description=>"If true, recalc fluxes when restarting timestep.",
586
+ :help=>"If true, recalc fluxes when restarting timestep.",
587
+ :code_name=>:flux_reset,
588
+ :must_pass=>
589
+ [{:test=>"kind_of? String and FORTRAN_BOOLS.include? self",
590
+ :explanation=>
591
+ "This variable must be a fortran boolean. (In Ruby this is represented as a string: e.g. '.true.')"}],
592
+ :type=>:Fortran_Bool},
593
+ :deltadj=>
594
+ {:should_include=>"true",
595
+ :description=>
596
+ "Factor by which to decrease the timestep when redoing step.",
597
+ :help=>"Factor by which to decrease the timestep when redoing step.",
598
+ :code_name=>:deltadj,
599
+ :must_pass=>
600
+ [{:test=>"kind_of? Numeric",
601
+ :explanation=>
602
+ "This variable must be a floating point number (an integer is also acceptable: it will be converted into a floating point number)."}],
603
+ :type=>:Float},
604
+ :avail_cpu_time=>
605
+ {:should_include=>"true",
606
+ :description=>"Available wall clock time in s.",
607
+ :help=>"Available wall clock time in s.",
608
+ :code_name=>:avail_cpu_time,
609
+ :must_pass=>
610
+ [{:test=>"kind_of? Numeric",
611
+ :explanation=>
612
+ "This variable must be a floating point number (an integer is also acceptable: it will be converted into a floating point number)."}],
613
+ :type=>:Float}}},
583
614
  :fluxes=>
584
615
  {:description=>"",
585
616
  :should_include=>"true",
@@ -794,7 +825,7 @@
794
825
  :explanation=>
795
826
  "This variable must be a floating point number (an integer is also acceptable: it will be converted into a floating point number)."}],
796
827
  :type=>:Float,
797
- :autoscanned_defaults=>[2, 2.0]},
828
+ :autoscanned_defaults=>[2.0]},
798
829
  :ddens=>
799
830
  {:should_include=>"true",
800
831
  :description=>" step size for density is -density * ddens",
@@ -1041,15 +1072,17 @@
1041
1072
  :autoscanned_defaults=>[-1]},
1042
1073
  :flux_driver=>
1043
1074
  {:should_include=>"true",
1044
- :description=>" If true, run flux simulations for the cell centres then quit ",
1045
- :help=>" If true, run flux simulations for the cell centres then quit",
1075
+ :description=>
1076
+ " If true, quit after get fluxes (i.e. use Trinity as driver for the flux calc)",
1077
+ :help=>
1078
+ " If true, quit after get fluxes (i.e. use Trinity as driver for the flux calc)",
1046
1079
  :code_name=>:flux_driver,
1047
1080
  :must_pass=>
1048
1081
  [{:test=>"kind_of? String and FORTRAN_BOOLS.include? self",
1049
1082
  :explanation=>
1050
1083
  "This variable must be a fortran boolean. (In Ruby this is represented as a string: e.g. '.true.')"}],
1051
1084
  :type=>:Fortran_Bool,
1052
- :autoscanned_defaults=>[".true."]},
1085
+ :autoscanned_defaults=>[".false."]},
1053
1086
  :ifspppl_bmag=>
1054
1087
  {:should_include=>"true",
1055
1088
  :description=>
@@ -1073,16 +1106,25 @@
1073
1106
  "This variable must be a fortran boolean. (In Ruby this is represented as a string: e.g. '.true.')"}],
1074
1107
  :type=>:Fortran_Bool,
1075
1108
  :autoscanned_defaults=>[]},
1076
- :flux_driver=>
1109
+ :flux_convergetol=>
1077
1110
  {:should_include=>"true",
1078
1111
  :description=>"",
1079
1112
  :help=>"",
1080
- :code_name=>:flux_driver,
1113
+ :code_name=>:flux_convergetol,
1081
1114
  :must_pass=>
1082
- [{:test=>"kind_of? String and FORTRAN_BOOLS.include? self",
1115
+ [{:test=>"kind_of? Numeric",
1083
1116
  :explanation=>
1084
- "This variable must be a fortran boolean. (In Ruby this is represented as a string: e.g. '.true.')"}],
1085
- :type=>:Fortran_Bool}}},
1117
+ "This variable must be a floating point number (an integer is also acceptable: it will be converted into a floating point number)."}],
1118
+ :type=>:Float},
1119
+ :dprim_option=>
1120
+ {:should_include=>"true",
1121
+ :description=>"",
1122
+ :help=>"",
1123
+ :code_name=>:dprim_option,
1124
+ :must_pass=>
1125
+ [{:test=>"kind_of? String",
1126
+ :explanation=>"This variable must be a string."}],
1127
+ :type=>:String}}},
1086
1128
  :init=>
1087
1129
  {:description=>"",
1088
1130
  :should_include=>"true",
@@ -1106,7 +1148,7 @@
1106
1148
  [{:test=>"kind_of? String",
1107
1149
  :explanation=>"This variable must be a string."}],
1108
1150
  :type=>:String,
1109
- :autoscanned_defaults=>["geo_file"]},
1151
+ :autoscanned_defaults=>["geo_file", "trim(init_file_temp)"]},
1110
1152
  :init_time=>
1111
1153
  {:should_include=>"true",
1112
1154
  :description=>" target time to sample experimental data (in seconds)",
@@ -1223,15 +1265,15 @@
1223
1265
  :restart_file=>
1224
1266
  {:should_include=>"true",
1225
1267
  :description=>
1226
- "Netcdf file with restart data (gradually replacing old restart files).",
1268
+ " name of the new netcdf restart file, set to 'old' for old restart",
1227
1269
  :help=>
1228
- "Netcdf file with restart data (gradually replacing old restart files).",
1270
+ " name of the new netcdf restart file, set to 'old' for old restart",
1229
1271
  :code_name=>:restart_file,
1230
1272
  :must_pass=>
1231
1273
  [{:test=>"kind_of? String",
1232
1274
  :explanation=>"This variable must be a string."}],
1233
1275
  :type=>:String,
1234
- :autoscanned_defaults=>[""]},
1276
+ :autoscanned_defaults=>["old"]},
1235
1277
  :iternt_file=>
1236
1278
  {:should_include=>"true",
1237
1279
  :description=>
@@ -1361,14 +1403,17 @@
1361
1403
  :autoscanned_defaults=>[-1.0]},
1362
1404
  :density_boost=>
1363
1405
  {:should_include=>"true",
1364
- :description=>"Increase initial density at constant pressure.",
1365
- :help=>"Increase initial density at constant pressure.",
1406
+ :description=>
1407
+ " If >0 then multiplies initial dens and divides initial temp (init_option_chease only)",
1408
+ :help=>
1409
+ " If >0 then multiplies initial dens and divides initial temp (init_option_chease only)",
1366
1410
  :code_name=>:density_boost,
1367
1411
  :must_pass=>
1368
1412
  [{:test=>"kind_of? Numeric",
1369
1413
  :explanation=>
1370
1414
  "This variable must be a floating point number (an integer is also acceptable: it will be converted into a floating point number)."}],
1371
- :type=>:Float}}},
1415
+ :type=>:Float,
1416
+ :autoscanned_defaults=>[-1.0]}}},
1372
1417
  :sources=>
1373
1418
  {:description=>"",
1374
1419
  :should_include=>"true",
@@ -1667,7 +1712,7 @@
1667
1712
  :explanation=>
1668
1713
  "This variable must be a fortran boolean. (In Ruby this is represented as a string: e.g. '.true.')"}],
1669
1714
  :type=>:Fortran_Bool,
1670
- :autoscanned_defaults=>[".true."]},
1715
+ :autoscanned_defaults=>[".false.", ".true."]},
1671
1716
  :temp_equil=>
1672
1717
  {:should_include=>"true",
1673
1718
  :description=>" set to false to neglect temperature equilibration",
@@ -1678,7 +1723,7 @@
1678
1723
  :explanation=>
1679
1724
  "This variable must be a fortran boolean. (In Ruby this is represented as a string: e.g. '.true.')"}],
1680
1725
  :type=>:Fortran_Bool,
1681
- :autoscanned_defaults=>[".true."]},
1726
+ :autoscanned_defaults=>[".false.", ".true."]},
1682
1727
  :turb_heat=>
1683
1728
  {:should_include=>"true",
1684
1729
  :description=>" set to false to neglect turbulent heating",
@@ -1689,7 +1734,7 @@
1689
1734
  :explanation=>
1690
1735
  "This variable must be a fortran boolean. (In Ruby this is represented as a string: e.g. '.true.')"}],
1691
1736
  :type=>:Fortran_Bool,
1692
- :autoscanned_defaults=>[".true."]},
1737
+ :autoscanned_defaults=>[".false.", ".true."]},
1693
1738
  :numult=>
1694
1739
  {:should_include=>"true",
1695
1740
  :description=>" multiplier of collision frequency for testing",
@@ -93,19 +93,26 @@ class NetcdfSmartReader
93
93
  elsif i=options[sym + :_element]
94
94
  return i
95
95
  elsif i=options[sym + :max]
96
+ #if sym == :conv
97
+ #return [i, @file.var('conv_final')
96
98
  return i
97
99
  else
98
100
  return -1
99
101
  end
100
102
  end
101
103
  def self.dimensions
102
- ['t','tspec', 'iter', 'rad', 'cc', 'mrow', 'mcol', 'ivar', 'jac', 'grad', 'eval', 'cegrid', 'job']
104
+ ['t','tspec', 'iter', 'rad', 'cc', 'mrow', 'mcol', 'ivar', 'jac', 'grad', 'eval', 'cegrid', 'job', 'conv']
103
105
  end
104
106
 
105
107
  def axiskit(variable, options)
106
108
  case variable
107
- when 'mrow', 'mcol', 'ivar', 'tspec', 'iter', 'jac', 'grad', 'eval', 'job'
108
- return GraphKit::AxisKit.autocreate(data: GSL::Vector.linspace(1, sz=@file.dim(variable).length, sz), title: variable)
109
+ when 'mrow', 'mcol', 'ivar', 'tspec', 'iter', 'jac', 'grad', 'eval', 'job', 'conv'
110
+ return GraphKit::AxisKit.autocreate(
111
+ data: GSL::Vector.linspace(1,
112
+ sz=
113
+ (de=dim_end(variable,options); de<0 ? de+@file.dim(variable).length : de) -
114
+ (ds=dim_start(variable,options); ds<0 ? ds+@file.dim(variable).length : ds) + 1,
115
+ sz), title: variable)
109
116
  end
110
117
  GraphKit::AxisKit.autocreate(data: read_variable(variable, options), units: @file.var(variable).att('units').get, title: @file.var(variable).att('description').get.sub(/(,|summed|average).*$/, '').sub(/[vV]alues of (the )?/, '').sub(/ coordinate/, '').sub(/.*rho.*The definition.*/, 'rho'))
111
118
  end
@@ -104,20 +104,6 @@ class CodeRunner
104
104
  def restart(new_run)
105
105
  #new_run = self.dup
106
106
  (rcp.variables).each{|v| new_run.set(v, send(v)) if send(v) or new_run.send(v)}
107
- if @flux_option == "gs2"
108
- flux_runs.each_with_index do |run, i|
109
- CodeRunner::Gs2.rcp.variables.each{|v|
110
- next if [:ginit_option, :delt_option].include? v and new_run.no_restart_gs2
111
- new_run.flux_runs[i].set(v, run.send(v)) if run.send(v) or new_run.flux_runs[i].send(v)
112
- }
113
- end
114
- elsif @flux_option == "gryfx"
115
- flux_runs.each_with_index do |run, i|
116
- CodeRunner::Gryfx.rcp.variables.each{|v|
117
- new_run.flux_runs[i].set(v, run.send(v)) if run.send(v) or new_run.flux_runs[i].send(v)
118
- }
119
- end
120
- end
121
107
  @naming_pars.delete(:preamble)
122
108
  SUBMIT_OPTIONS.each{|v| new_run.set(v, self.send(v)) unless new_run.send(v)}
123
109
  #(rcp.results + rcp.gs2_run_info).each{|result| new_run.set(result, nil)}
@@ -130,16 +116,35 @@ class CodeRunner
130
116
  new_run.itercalib_file = @run_name + ".itercalib"
131
117
  new_run.restart_file = @run_name + ".out.nc"
132
118
  new_run.init_file = @run_name + ".tmp"
133
- @runner.nprocs = @nprocs if @runner.nprocs == "1" # 1 is the default so this means the user probably didn't specify nprocs
134
- # This is unnecessary for single restart file.
135
- warning( "Restart is not on the same number of processors as the previous run: new is #{new_run.nprocs.inspect} and old is #{@nprocs.inspect}... this is only OK if you are using parallel netcdf and single restart files.") if flux_gs2? and not new_run.no_restart_gs2 and (!new_run.nprocs or new_run.nprocs != @nprocs)
136
- raise "Restart cannot have a different sized jacobian: new is #{new_run.n_flux_tubes_jac} and old is #{n_flux_tubes_jac}" unless new_run.n_flux_tubes_jac == n_flux_tubes_jac
119
+ new_run.flux_pars = @flux_pars.absorb(new_run.flux_pars) if @flux_pars and new_run.flux_pars
137
120
  new_run.run_name = nil
138
121
  new_run.naming_pars = @naming_pars
139
122
  new_run.update_submission_parameters(new_run.parameter_hash.inspect, false) if new_run.parameter_hash
140
123
  new_run.naming_pars.delete(:restart_id)
141
124
  new_run.generate_run_name
142
125
  new_run.run_name += '_t'
126
+ if @flux_option == "gs2"
127
+ flux_runs.each_with_index do |run, i|
128
+ CodeRunner::Gs2.rcp.variables.each{|v|
129
+ next if [:ginit_option, :delt_option].include? v and new_run.no_restart_gs2
130
+ new_run.flux_runs[i].set(v, run.send(v)) if run.send(v) or new_run.flux_runs[i].send(v)
131
+ }
132
+ end
133
+ elsif @flux_option == "gryfx"
134
+ flux_runs.each_with_index do |run, i|
135
+ CodeRunner::Gryfx.rcp.variables.each{|v|
136
+ new_run.flux_runs[i].set(v, run.send(v)) if run.send(v) or new_run.flux_runs[i].send(v)
137
+ }
138
+ eputs "parameter_hash_string is " + new_run.flux_runs[i].parameter_hash_string
139
+ eputs "nwrite is ", new_run.flux_runs[i].nwrite, run.nwrite
140
+ new_run.flux_runs[i].update_submission_parameters(new_run.flux_runs[i].parameter_hash_string, false)
141
+ eputs "nwrite 2 is ", new_run.flux_runs[i].nwrite, run.nwrite
142
+ end
143
+ end
144
+ @runner.nprocs = @nprocs if @runner.nprocs == "1" # 1 is the default so this means the user probably didn't specify nprocs
145
+ # This is unnecessary for single restart file.
146
+ warning( "Restart is not on the same number of processors as the previous run: new is #{new_run.nprocs.inspect} and old is #{@nprocs.inspect}... this is only OK if you are using parallel netcdf and single restart files.") if flux_gs2? and not new_run.no_restart_gs2 and (!new_run.nprocs or new_run.nprocs != @nprocs)
147
+ raise "Restart cannot have a different sized jacobian: new is #{new_run.n_flux_tubes_jac} and old is #{n_flux_tubes_jac}" unless new_run.n_flux_tubes_jac == n_flux_tubes_jac
143
148
  eputs 'Copying Trinity Restart files', ''
144
149
  #system "ls #@directory"
145
150
  ['iternt', 'iterflx', 'tmp', 'itercalib', 'out.nc'].each do |ext|
@@ -156,6 +161,7 @@ class CodeRunner
156
161
 
157
162
  new_run.flux_runs[i].directory = new_run.directory + "/#{folder}"
158
163
  FileUtils.makedirs(new_run.flux_runs[i].directory)
164
+
159
165
  flux_runs[i].restart(new_run.flux_runs[i])
160
166
  if new_run.neval_calibrate and new_run.neval_calibrate > 0 and
161
167
  new_run.flux_runs[i].nonlinear_mode == "off"
@@ -171,12 +177,30 @@ class CodeRunner
171
177
  for i in 0...n_flux_tubes
172
178
  break if i >= new_run.n_flux_tubes
173
179
  next if not FileTest.exist? flux_runs[i].directory + '/' + flux_runs[i].run_name + '.restart.cdf'
180
+ eputs " old new nx #{flux_runs[i].nx} #{new_run.flux_runs[i].nx}"
181
+ if not (
182
+ flux_runs[i].nx == new_run.flux_runs[i].nx and
183
+ flux_runs[i].ny == new_run.flux_runs[i].ny and
184
+ flux_runs[i].ngauss == new_run.flux_runs[i].ngauss and
185
+ flux_runs[i].negrid == new_run.flux_runs[i].negrid and
186
+ flux_runs[i].ntheta == new_run.flux_runs[i].ntheta
187
+ )
188
+ new_run.flux_runs[i].ginit_option = "noise"
189
+ new_run.flux_runs[i].delt_option = "default"
190
+ new_run.flux_runs[i].is_a_restart = false
191
+ new_run.flux_runs[i].restart_id = nil
192
+ new_run.flux_runs[i].restart = "off"
193
+ next
194
+ end
174
195
  folder = flux_folder_name(i)
175
196
  new_run.flux_runs[i].directory = new_run.directory + "/#{folder}"
176
197
  FileUtils.makedirs(new_run.flux_runs[i].directory)
198
+ #eputs "nwrite 3 is ", new_run.flux_runs[i].nwrite
177
199
  flux_runs[i].set_restart(new_run.flux_runs[i])
200
+ #eputs "nwrite 4 is ", new_run.flux_runs[i].nwrite
178
201
  end
179
202
  end
203
+ #eputs "nwrite 5 is ", new_run.flux_runs[0].nwrite
180
204
  new_run
181
205
  end
182
206
  # This is a hook which gets called just before submitting a simulation. It sets up the folder and generates any necessary input files.
@@ -190,9 +214,16 @@ class CodeRunner
190
214
  elsif uses_chease?
191
215
  setup_chease
192
216
  end
217
+ #eputs "nwrite 6 is ", new_run.flux_runs[0].nwrite
218
+ @avail_cpu_time = (@wall_mins-1.0) * 60 if @wall_mins
219
+ if flux_gs2? or flux_gryfx?
220
+ @avail_cpu_time = (@wall_mins-6.0) * 60 if @wall_mins
221
+ end
193
222
  check_parameters
194
223
  write_input_file
224
+ #eputs "nwrite 7 is ", new_run.flux_runs[0].nwrite
195
225
  generate_flux_input_files if flux_gs2? or flux_gryfx?
226
+ #eputs "nwrite 8 is ", new_run.flux_runs[0].nwrite
196
227
  end
197
228
 
198
229
 
@@ -249,15 +280,16 @@ class CodeRunner
249
280
  # Override CodeRunner::Run method to deal with flux_pars properly
250
281
  # when generating run_name
251
282
  def generate_run_name
252
- @run_name = %[v#@version] + @naming_pars.inject("") do |str, par|
253
- case par
254
- when :flux_pars
255
- str+="_flx_#{send(par).map{|k,v| "#{k}_#{v.to_s[0..8]}"}.join("_")}}"
256
- else
257
- str+="_#{par}_#{send(par).to_s[0...8]}"
258
- end
283
+ return super if CodeRunner::GLOBAL_OPTIONS[:short_run_name]
284
+ @run_name = %[v#@version] + @naming_pars.inject("") do |str, par|
285
+ case par
286
+ when :flux_pars
287
+ str+="_flx_#{send(par).map{|k,v| "#{k}_#{v.to_s[0..8]}"}.join("_")}}"
288
+ else
289
+ str+="_#{par}_#{send(par).to_s[0...8]}"
259
290
  end
260
- @run_name = @run_name.gsub(/\s+/, "_").gsub(/[\/{}"><:=]/, '') + "_id_#@id"
291
+ end
292
+ @run_name = @run_name.gsub(/\s+/, "_").gsub(/[\/{}"><:=]/, '') + "_id_#@id"
261
293
  end
262
294
 
263
295
  # The number of separate flux tube results needed for the jacobian
@@ -473,6 +505,20 @@ class CodeRunner
473
505
  end
474
506
  end
475
507
 
508
+ def is_converged?
509
+ Dir.chdir(@directory) do
510
+ if FileTest.exist?(new_netcdf_filename) and
511
+ @convergetol and
512
+ new_netcdf_file.var('convergeval') and
513
+ new_netcdf_file.dim('t').length > 2 and
514
+ new_netcdf_file.var('convergeval').get[0,-1] < @convergetol
515
+ return true
516
+ else
517
+ return false
518
+ end
519
+ end
520
+ end
521
+
476
522
 
477
523
  @source_code_subfolders = []
478
524
 
data/trinitycrmod.gemspec CHANGED
@@ -2,16 +2,16 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: trinitycrmod 0.8.11 ruby lib
5
+ # stub: trinitycrmod 0.8.12 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "trinitycrmod"
9
- s.version = "0.8.11"
9
+ s.version = "0.8.12"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib"]
13
13
  s.authors = ["Edmund Highcock"]
14
- s.date = "2016-04-01"
14
+ s.date = "2016-06-09"
15
15
  s.description = "This module allows Trinity, the Multiscale Gyrokinetic Turbulent Transport solver for Fusion Reactors, to harness the power of CodeRunner, a framework for the automated running and analysis of simulations."
16
16
  s.email = "edmundhighcock@sourceforge.net"
17
17
  s.extra_rdoc_files = [
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: trinitycrmod
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.11
4
+ version: 0.8.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Edmund Highcock
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-04-01 00:00:00.000000000 Z
11
+ date: 2016-06-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: coderunner