Skip to content

Commit 89aa289

Browse files
committed
scenario test green
1 parent 41b82eb commit 89aa289

5 files changed

Lines changed: 179 additions & 24 deletions

File tree

prms_python/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
from .parameters import Parameters, modify_params
22
from .simulation import Simulation
3+
from .scenario import Scenario

prms_python/prms_python.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
relevant provenance information and input files to facilitate better
1212
data management techniques to streamline later analyses.
1313
'''
14-
import copy
1514
import datetime
1615
import itertools
1716
import numpy as np

prms_python/scenario.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import inspect
2+
import json
3+
import os
4+
import shutil
5+
6+
from datetime import datetime
7+
8+
from .parameters import modify_params
9+
from .simulation import Simulation
10+
11+
12+
class Scenario:
13+
"""
14+
Container for the process in which one modifies some base files then
15+
runs a simulation on the modified files. This also tracks metadata,
16+
including a title and description, if provided, plus start/end datetime
17+
and strings of the modification functions python code.
18+
"""
19+
20+
def __init__(self, base_dir, scenario_dir,
21+
title=None, description=None):
22+
23+
self.title = title
24+
self.description = description
25+
26+
self.base_dir = base_dir
27+
self.scenario_dir = scenario_dir
28+
29+
self.metadata = ScenarioMetadata(title=title, description=description)
30+
31+
self.__simulation_ready = False
32+
33+
def build_scenario(self, param_mod_funs=None):
34+
35+
if not isinstance(param_mod_funs, dict):
36+
raise TypeError('param_mod_funs must be a dictionary')
37+
38+
# create scenario_dir that will be used as Simulation input dir
39+
if os.path.isdir(self.scenario_dir):
40+
shutil.rmtree(self.scenario_dir)
41+
42+
os.makedirs(self.scenario_dir)
43+
shutil.copy(
44+
os.path.join(self.base_dir, 'control'), self.scenario_dir
45+
)
46+
shutil.copy(
47+
os.path.join(self.base_dir, 'data'), self.scenario_dir
48+
)
49+
50+
old_params_path = os.path.join(self.base_dir, 'parameters')
51+
new_params_path = os.path.join(self.scenario_dir, 'parameters')
52+
if not param_mod_funs:
53+
shutil.copy(old_params_path, self.scenario_dir)
54+
else:
55+
modify_params(old_params_path, new_params_path, param_mod_funs)
56+
57+
param_mod_funs_metadata = {
58+
param_name: inspect.getsource(param_mod_fun)
59+
for param_name, param_mod_fun in param_mod_funs.iteritems()
60+
}
61+
62+
self.metadata['mod_funs_dict'] = param_mod_funs_metadata
63+
64+
self.simulation = Simulation(self.scenario_dir, self.scenario_dir)
65+
66+
self.__simulation_ready = True
67+
68+
def run(self):
69+
70+
if not self.__simulation_ready:
71+
raise RuntimeError(
72+
'Scenario has not yet been prepared: run build_scenario first'
73+
)
74+
75+
self.metadata['start_datetime'] = datetime.now().isoformat()
76+
self.simulation.run()
77+
self.metadata['end_datetime'] = datetime.now().isoformat()
78+
79+
self.metadata.write(os.path.join(self.scenario_dir, 'metadata.json'))
80+
81+
82+
class ScenarioMetadata:
83+
84+
def __init__(self, title=None, description=None, start_datetime=None,
85+
end_datetime=None, mod_funs_dict=None):
86+
87+
self.metadata_dict = dict(title=title,
88+
description=description,
89+
start_datetime=start_datetime,
90+
end_datetime=end_datetime,
91+
mod_funs_dict=mod_funs_dict)
92+
93+
def __getitem__(self, key):
94+
return self.metadata_dict[key]
95+
96+
def __setitem__(self, key, value):
97+
self.metadata_dict[key] = value
98+
99+
def write(self, output_path):
100+
with open(output_path, 'w') as f:
101+
f.write(json.dumps(self.metadata_dict))

prms_python/simulation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def __init__(self, input_dir, simulation_dir=None):
4242
raise RuntimeError('Data file missing from ' + idir)
4343

4444
self.simulation_dir = simulation_dir
45-
if simulation_dir:
45+
if simulation_dir and simulation_dir != input_dir:
4646

4747
if os.path.exists(simulation_dir):
4848
shutil.rmtree(simulation_dir)

test/test_prms_python.py

Lines changed: 76 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
import json
12
import glob
23
import os
34
import shutil
45
import unittest
56

67
from difflib import Differ
78

8-
from prms_python import modify_params, Parameters, Simulation
9+
from prms_python import modify_params, Parameters, Scenario, Simulation
910

1011

1112
class TestSimulations(unittest.TestCase):
@@ -55,43 +56,96 @@ def test_simulation_w_simdir(self):
5556
self.assertIn('inputs', gs)
5657
self.assertIn('outputs', gs)
5758

58-
gi = [
59-
os.path.basename(f)
60-
for f in
61-
glob.glob(os.path.join(self.simulation_dir, 'inputs', '*'))
62-
]
63-
self.assertIn('control', gi)
64-
self.assertIn('parameters', gi)
65-
self.assertIn('data', gi)
66-
67-
go = [
68-
os.path.basename(f)
69-
for f in
70-
glob.glob(os.path.join(self.simulation_dir, 'outputs', '*'))
71-
]
72-
self.assertIn('prms_ic.out', go)
73-
self.assertIn('prms.out', go)
74-
self.assertIn('statvar.dat', go)
75-
self.assertIn('animation.out.nhru', go)
59+
assert_valid_input_dir(
60+
self, os.path.join(self.simulation_dir, 'inputs')
61+
)
62+
assert_valid_output_dir(
63+
self, os.path.join(self.simulation_dir, 'outputs')
64+
)
7665

7766

7867
class TestScenarios(unittest.TestCase):
7968

8069
def setUp(self):
81-
pass
70+
71+
self.test_data_dir = os.path.join('test', 'data')
72+
73+
self.test_model_data_dir = os.path.join(
74+
'test', 'data', 'models', 'lbcd'
75+
)
76+
77+
self.scenario_dir = os.path.join(self.test_data_dir, 'tmp_scenario')
8278

8379
def tearDown(self):
84-
pass
80+
81+
if os.path.exists(self.scenario_dir):
82+
shutil.rmtree(self.scenario_dir)
8583

8684
def test_create_scenario(self):
8785
"""a simulation setup should create a simulation directory"""
88-
assert False
86+
87+
s = Scenario(
88+
self.test_model_data_dir, self.scenario_dir,
89+
title='Scenario Uno', description='test scenario for prms_python'
90+
)
91+
92+
param_mods = {
93+
'snow_adj': lambda x: 1.1*x,
94+
'rad_trncf': lambda x: 0.9*x
95+
}
96+
s.build_scenario(param_mod_funs=param_mods)
97+
98+
assert_valid_input_dir(self, self.scenario_dir) # os.path.join(self.scenario_dir, 'inputs'))
99+
100+
s.run()
101+
102+
assert_valid_input_dir(
103+
self, os.path.join(self.scenario_dir, 'inputs')
104+
)
105+
assert_valid_output_dir(
106+
self, os.path.join(self.scenario_dir, 'outputs')
107+
)
108+
109+
md_json_path = os.path.join(self.scenario_dir, 'metadata.json')
110+
assert os.path.isfile(md_json_path)
111+
112+
md_json = json.loads(open(md_json_path).read())
113+
assert md_json['title'] == 'Scenario Uno'
114+
assert md_json['description'] == 'test scenario for prms_python'
115+
assert 'start_datetime' in md_json
116+
assert 'end_datetime' in md_json
117+
assert 'mod_funs_dict' in md_json
89118

90119
def test_create_many_scenarios(self):
91120
"create_many_simulations should create many simulation directories"
92121
assert False
93122

94123

124+
def assert_valid_input_dir(test_case, d):
125+
126+
gs = [
127+
os.path.basename(f)
128+
for f in glob.glob(os.path.join(d, '*'))
129+
]
130+
131+
test_case.assertIn('control', gs, d)
132+
test_case.assertIn('parameters', gs, d)
133+
test_case.assertIn('data', gs, d)
134+
135+
136+
def assert_valid_output_dir(test_case, d):
137+
138+
go = [
139+
os.path.basename(f)
140+
for f in
141+
glob.glob(os.path.join(d, '*'))
142+
]
143+
test_case.assertIn('prms_ic.out', go)
144+
test_case.assertIn('prms.out', go)
145+
test_case.assertIn('statvar.dat', go)
146+
test_case.assertIn('animation.out.nhru', go)
147+
148+
95149
class TestParameters(unittest.TestCase):
96150

97151
def setUp(self):

0 commit comments

Comments
 (0)