def main(): # Parse command line arguments. parser = argparse.ArgumentParser( description= 'This script open an interactive tree view with all settings of a biogeochemical model.' ) parser.add_argument( 'path', help= 'Path to a YAML file with the model configuration (typically fabm.yaml)', nargs='?', default='fabm.yaml') args = parser.parse_args() # Create model object model = pyfabm.Model(args.path) # Create Qt application object app = QtGui.QApplication([' ']) # Create dialog box with model configuration tree. dialog = QtGui.QDialog() dialog.setWindowTitle('Configure model') layout = QtGui.QHBoxLayout() tree = pyfabm.gui_qt.TreeView(model, dialog) layout.addWidget(tree) dialog.setLayout(layout) dialog.show() # Show dialog ret = app.exec_()
def test_pyfabm(args, testcases): build_dir = os.path.join(args.work_root, 'build') if not cmake('test_pyfabm', build_dir, os.path.join(fabm_base, 'src/drivers/python'), args.cmake, cmake_arguments=[ '-DCMAKE_BUILD_TYPE=debug', '-DPYTHON_EXECUTABLE=%s' % sys.executable ] + args.cmake_arguments): return sys.path.insert(0, build_dir) import pyfabm dependency_names = set() print('pyfabm loaded from %s (library = %s)' % (pyfabm.__file__, pyfabm.dllpath)) print('Running FABM testcases with pyfabm:') for case, path in testcases.items(): print(' %s... ' % case, end='') sys.stdout.flush() m = pyfabm.Model(path) m.cell_thickness = 1. for d in m.dependencies: dependency_names.add(d.name) d.value = 1. m.start() m.getRates() print('SUCCESS') try: pyfabm.unload() except Exception as e: print('Failed to unload pyfabm: %s' % e) if args.verbose: print('Combined dependency list:\n%s' % '\n'.join(sorted(dependency_names)))
def main(): import argparse parser = argparse.ArgumentParser(description=__doc__) parser.add_argument( 'path', help= 'Path to a YAML file with the model configuration (typically fabm.yaml)', nargs='?', default='fabm.yaml') parser.add_argument( '--all', '-a', action='store_true', help='Show diagnostics that are by default excluded from output') args = parser.parse_args() # Create model object from YAML file. model = pyfabm.Model(args.path) print('Interior state variables:') for variable in model.interior_state_variables: print(' %s = %s (%s)' % (variable.name, variable.long_name, variable.units)) print('Surface-attached state variables:') for variable in model.surface_state_variables: print(' %s = %s (%s)' % (variable.name, variable.long_name, variable.units)) print('Bottom-attached state variables:') for variable in model.bottom_state_variables: print(' %s = %s (%s)' % (variable.name, variable.long_name, variable.units)) print('Interior diagnostic variables:') for variable in model.interior_diagnostic_variables: if variable.output or args.all: print(' %s = %s (%s)' % (variable.name, variable.long_name, variable.units)) print('Horizontal diagnostic variables:') for variable in model.horizontal_diagnostic_variables: if variable.output or args.all: print(' %s = %s (%s)' % (variable.name, variable.long_name, variable.units)) print('Conserved quantities:') for variable in model.conserved_quantities: print(' %s (%s)' % (variable.name, variable.units)) print('Dependencies:') for variable in model.dependencies: print(' %s = %s (%s)' % (variable.name, variable.long_name, variable.units))
def main(): parser = argparse.ArgumentParser(description='This script lists all state variables, diagnostic variables, conserved quantities and environmental dependencies of a biogeochemical model.') parser.add_argument('path',help='Path to a YAML file with the model configuration (typically fabm.yaml)',nargs='?',default='fabm.yaml') args = parser.parse_args() # Create model object from YAML file. model = pyfabm.Model(args.path) print 'Interior state variables:' for variable in model.bulk_state_variables: print ' %s = %s (%s)' % (variable.name,variable.long_name,variable.units) print 'Surface-attached state variables:' for variable in model.surface_state_variables: print ' %s = %s (%s)' % (variable.name,variable.long_name,variable.units) print 'Bottom-attached state variables:' for variable in model.bottom_state_variables: print ' %s = %s (%s)' % (variable.name,variable.long_name,variable.units) print 'Interior diagnostic variables:' for variable in model.bulk_diagnostic_variables: print ' %s = %s (%s)' % (variable.name,variable.long_name,variable.units) print 'Horizontal diagnostic variables:' for variable in model.horizontal_diagnostic_variables: print ' %s = %s (%s)' % (variable.name,variable.long_name,variable.units) print 'Conserved quantities:' for variable in model.conserved_quantities: print ' %s (%s)' % (variable.name,variable.units) print 'Dependencies:' for variable in model.dependencies: print ' %s = %s (%s)' % (variable.name,variable.long_name,variable.units)
def main(): import argparse tests = { 'randomized': testRandomized, 'extremes_per_variable': testExtremes, 'extremes_randomized': testRandomizedExtremes, 'extremes_all': testExtremesRecursive } parser = argparse.ArgumentParser( description= 'This script verifies that a biogeochemical model returns valid derivatives under a wide variety of inputs (state variables, environmental dependencies). Different tests can be run, using either random values or extremes for inputs, and running randomized or exhaustive tests.' ) parser.add_argument( 'model_path', help= 'Path to a YAML file with the model configuration (typically fabm.yaml)' ) parser.add_argument( 'ranges_path', help= 'Path to a YAML file with ranges for all model inputs (state variables, environmental dependencies)' ) parser.add_argument( '--test', choices=tests.keys(), action='append', help= 'Path to a YAML file with ranges for all model inputs (state variables, environmental dependencies)' ) parser.add_argument('--write-ranges', action='store_true', help='Write ranges file with default variable ranges') args = parser.parse_args() # Create model object from YAML file. model = pyfabm.Model(args.model_path) if args.write_ranges: with open(args.ranges_path, 'w') as f: def writeRanges(variables): for variable in variables: f.write('%s: [0,%s]\n' % (variable.name, 10 * variable.value)) writeRanges(model.state_variables) writeRanges(model.dependencies) print('Default ranges have been written to %s.' % args.ranges_path) sys.exit(0) with open(args.ranges_path, 'rU') as f: ranges = yaml.load(f) if not isinstance(ranges, dict): print( 'Range file %s should contain a dictionary mapping each variable to its range (or constant value).' % args.ranges_path) sys.exit(1) vary = [] found_variables = set() testRangePresence(ranges, model.state_variables, vary, found_variables) testRangePresence(ranges, model.dependencies, vary, found_variables) for variable_name in ranges.keys(): if variable_name not in found_variables: print( 'WARNING: range specification for unknown variable %s in %s will be ignored.' % (variable_name, args.ranges_path)) if args.test is None: check(model) else: for test in args.test: tests[test](model, vary)
sys.exit(1) parser = argparse.ArgumentParser( description= 'This script lists all state variables, diagnostic variables, conserved quantities and environmental dependencies of a biogeochemical model.' ) parser.add_argument( 'path', help= 'Path to a YAML file with the model configuration (typically fabm.yaml)', nargs='?', default='fabm.yaml') args = parser.parse_args() # Create model object from YAML file. model = pyfabm.Model(args.path) print 'Interior state variables:' for variable in model.bulk_state_variables: print ' %s = %s (%s)' % (variable.name, variable.long_name, variable.units) print 'Surface-attached state variables:' for variable in model.surface_state_variables: print ' %s = %s (%s)' % (variable.name, variable.long_name, variable.units) print 'Bottom-attached state variables:' for variable in model.bottom_state_variables: print ' %s = %s (%s)' % (variable.name, variable.long_name, variable.units)
print 'Unable to load pyfabm. Please build and install FABM with FABM_HOST=python.' sys.exit(1) import pyfabm.gui_qt from PySide import QtCore,QtGui # Parse command line arguments. parser = optparse.OptionParser() options, args = parser.parse_args() if len(sys.argv)!=2: print 'This script takes one argument: the path to a YAML file with FABM settings (typically fabm.yaml).' sys.exit(2) yamlfile = sys.argv[1] # Create model object model = pyfabm.Model(yamlfile) # Create Qt application object app = QtGui.QApplication([' ']) # Create dialog box with model configuration tree. dialog = QtGui.QDialog() dialog.setWindowTitle('Configure model') layout = QtGui.QHBoxLayout() tree = pyfabm.gui_qt.TreeView(model,dialog) layout.addWidget(tree) dialog.setLayout(layout) dialog.show() # Show dialog ret = app.exec_()
def evaluate(yaml_path, sources=(), location={}, assignments={}, verbose=True, ignore_missing=False, surface=True, bottom=True): # Create model object from YAML file. model = pyfabm.Model(yaml_path) allvariables = list(model.state_variables) + list(model.dependencies) name2variable = {} for variable in allvariables: name2variable[variable.name] = variable if hasattr(variable, 'output_name'): name2variable[variable.output_name] = variable lcname2variable = dict([(name.lower(), variable) for (name, variable) in name2variable.items()]) def set_state(**dim2index): missing = set(allvariables) variable2source = {} def set_variable(variable, value, source): missing.discard(variable) if variable in variable2source: print 'WARNING: %s = %s set by %s is overwritten with %s set by %s' % (variable.name, variable.value, variable2source[variable], value, source) variable2source[variable] = source variable.value = value for path in sources: if path.endswith('yaml'): with open(path) as f: data = yaml.load(f) for name, value in data.items(): variable = name2variable.get(name) if variable is None: variable = lcname2variable.get(name.lower()) if variable is None: print 'ERROR: variable "%s" specified in %s not found in model' % (name, path) sys.exit(1) set_variable(variable, float(value), path) else: with netCDF4.Dataset(path) as nc: for variable in allvariables: if variable.output_name not in nc.variables: continue ncvar = nc.variables[variable.output_name] indices = [] for dim, length in zip(ncvar.dimensions, ncvar.shape): index = 0 if length > 1: if dim not in dim2index: print 'ERROR: Dimension %s of %s has length > 1; an index must be specified with %s=INDEX' % (dim, variable.output_name, dim) sys.exit(1) index = dim2index[dim] indices.append(index) set_variable(variable, float(ncvar[tuple(indices)]), path) for name, value in assignments.items(): if name not in name2variable: print 'Explicitly specified variable "%s" not found in model.' % name sys.exit(2) variable = name2variable[name] missing.discard(variable) variable2source[variable] = 'command line' variable.value = float(value) if verbose: print print 'State:' for variable in sorted(model.state_variables, cmp=lambda x, y: cmp(x.name.lower(), y.name.lower())): print ' %s: %s [%s]' % (variable.name, variable.value, variable2source.get(variable)) print 'Environment:' for variable in sorted(model.dependencies, cmp=lambda x, y: cmp(x.name.lower(), y.name.lower())): print ' %s: %s [%s]' % (variable.name, variable.value, variable2source.get(variable)) if missing: print 'The following variables are still missing:' for variable in sorted(missing, cmp=lambda x, y: cmp(x.name.lower(), y.name.lower())): print '- %s' % variable.name, if variable.name != variable.output_name: print '(NetCDF: %s)' % variable.output_name, print return missing missing = set_state(**location) if missing and not ignore_missing: sys.exit(1) print 'State variables with largest value:' for variable in sorted(model.state_variables, cmp=lambda x, y: cmp(abs(y.value), abs(x.value)))[:3]: print ' %s: %s %s' % (variable.name, variable.value, variable.units) # Get model rates rates = model.getRates(surface=surface, bottom=bottom) assert len(rates) == len(model.state_variables), 'Length of array with rates does not match number of state variables' if verbose: print 'Diagnostics:' for variable in sorted(model.diagnostic_variables, cmp=lambda x, y: cmp(x.name.lower(), y.name.lower())): if variable.output: print ' %s: %s %s' % (variable.name, variable.value, variable.units) # Check whether rates of change are valid numbers valids = numpy.isfinite(rates) if not valids.all(): print 'The following state variables have an invalid rate of change:' for variable, rate, valid in zip(model.state_variables, rates, valids): if not valid: print ' %s: %s' % (variable.name, rate) eps = 1e-30 relative_rates = numpy.array([rate/(variable.value+eps) for variable, rate in zip(model.state_variables, rates)]) if verbose: # Show all rates of change, odered by their value relative to the state variable's value. print 'Relative rates of change (low to high):' for variable, rate, relative_rate in sorted(zip(model.state_variables, rates, relative_rates), cmp=lambda x, y: cmp(x[2], y[2])): print ' %s: %s d-1' % (variable.name, 86400*relative_rate) print 'Largest relative rates of change:' for variable, rate, relative_rate in sorted(zip(model.state_variables, rates, relative_rates), cmp=lambda x, y: cmp(abs(y[2]), abs(x[2])))[:3]: print ' %s: %s d-1' % (variable.name, 86400*relative_rate) i = relative_rates.argmin() print 'Minimum time step = %.3f s due to decrease in %s' % (-1./relative_rates[i], model.state_variables[i].name)
def processFile(infile,outfile,subtract_background=False,add_missing=False): # Create model object from YAML file. model = pyfabm.Model(infile) # Load the old configuration with open(infile,'rU') as f: config = yaml.load(f) def findMaximumDepth(d): n = 0 for key,value in d.items(): if isinstance(value,dict): l = 2+findMaximumDepth(value) else: l = len(key)+2+len(str(value)) # key, followed by ": ", followed by value n = max(n,l) return n def reorderParameters(modelname,parameters): """This reorders the parameters and adjusts the letter case of parameter names so that both match their declaration in the code.""" newparameters = collections.OrderedDict() parameters_lower = dict([(key.lower(),value) for key,value in parameters.items()]) modelname = modelname.lower() for parameter in model.parameters: if parameter.name.lower().startswith(modelname+'/'): name = parameter.name[len(modelname)+1:] name_lc = name.lower() if name_lc in parameters_lower: newparameters[name] = parameters_lower[name_lc] assert len(newparameters)==len(parameters) return newparameters def addMissingParameters(modelname,parameters): """This adds all parameters that have a default specified in the code but are missing from the yaml file.""" parameters_lower = frozenset([key.lower() for key in parameters.keys()]) modelname = modelname.lower() for parameter in model.parameters: if parameter.name.lower().startswith(modelname+'/'): name = parameter.name[len(modelname)+1:] if name.lower() not in parameters_lower and '/' not in name and parameter.default is not None: parameters[name] = parameter.default def reorderCouplings(modelname,variables): newvariables = collections.OrderedDict() modelname = modelname.lower() # First insert all couplings we do not understand (e.g., to whole models) couplings_lower = set([coupling.name.lower() for coupling in model.couplings]) for name in variables.keys(): if modelname+'/'+name.lower() not in couplings_lower: newvariables[name] = variables[name] variables_lower = dict([(key.lower(),value) for key,value in variables.items()]) for coupling in model.couplings: if coupling.name.lower().startswith(modelname+'/'): name = coupling.name[len(modelname)+1:] name_lower = name.lower() if name_lower in variables_lower: newvariables[name] = variables_lower[name_lower] return newvariables def python2yaml(value): if value is None: return '' if isinstance(value,bool): return 'true' if value else 'false' return str(value) def processDict(f,d,path=[]): # If processing parameter list, reorder according to their registration by the model. if len(path)==3: if path[-1]=='parameters': d = reorderParameters(path[1],d) if len(d)==0: return elif path[-1]=='coupling': d = reorderCouplings(path[1],d) # If processing model instances list, first wield out models with use=False if len(path)==1 and path[0]=='instances': for key in d.keys(): instance = d[key] if isinstance(instance,dict) and not instance.get('use',True): del d[key] # If processing a model dictionary, reorder according to prescribed order. if len(path)==2 and path[0]=='instances': d.pop('use',None) if add_missing: addMissingParameters(path[1],d.setdefault('parameters',collections.OrderedDict())) if not d.get('parameters',{}): d.pop('parameters',None) newd = collections.OrderedDict() order = ('long_name','model','parameters','initialization','coupling') for key in order: if key in d: newd[key] = d.pop(key) assert not d,'Model "%s" contains unknown keys %s' % (path[1],', '.join(d.keys())) d = newd nspace = len(path)*2 for key, value in d.items(): f.write(' '*nspace) if isinstance(value,dict): f.write('%s:\n' % key) processDict(f,value,path=path+[key]) else: metadata = None if len(path)==3: if path[-1]=='parameters': metadata = model.findParameter(path[1]+'/'+key) value = metadata.value elif path[-1]=='initialization': metadata = model.findStateVariable(path[1]+'/'+key) value = metadata.value if subtract_background: value -= metadata.background_value elif path[-1]=='coupling': try: metadata = model.findCoupling(path[1]+'/'+key) except KeyError: # If YAML coupling was not found, it typically is a coupling to a model rather than to a variable. Ignore this. pass value = python2yaml(value) if metadata is not None: f.write('%s: %s' % (metadata.name[len(path[1])+1:],value)) l = nspace+len(key)+2+len(str(value)) f.write('%s# %s' % (' '*(icommentstart-l),metadata.long_name,)) if metadata.units: f.write(' (%s)' % (metadata.units,)) if getattr(metadata,'default',None) is not None: f.write(', default = %s' % (python2yaml(metadata.default),)) else: f.write('%s: %s' % (key,value)) f.write('\n') icommentstart = findMaximumDepth(config)+3 with open(outfile,'w') as f: processDict(f,config)
def __init__(self, parameters={}, prey=(), temperature=None, recruitment_from_prey=0, fabm_yaml_path=None, depth=None, initial_density=1., verbose=True): self.parameters = dict(parameters) self.initial_density = initial_density self.temperature_provider = None if temperature is not None: self.temperature_provider = datasources.asValueProvider( temperature) self.depth_provider = None if depth is not None: self.depth_provider = datasources.asValueProvider(depth) assert not pyfabm.hasError( ), 'pyfabm library has crashed previously (stop has been called).' #fabm_yaml_path = 'fabm.yaml' if fabm_yaml_path is None: fabm_yaml_fd, fabm_yaml_path = tempfile.mkstemp() fabm_yaml_file = os.fdopen(fabm_yaml_fd, 'w') else: fabm_yaml_file = open(fabm_yaml_path, 'w') mizer_params = dict(parameters) if depth is not None: mizer_params['biomass_has_prey_unit'] = False mizer_coupling = {'waste': 'zero_hz'} mizer_initialization = {} for iclass in range(mizer_params['nclass']): mizer_initialization[ 'Nw%i' % (iclass + 1, )] = initial_density / mizer_params['nclass'] mizer_yaml = { 'model': 'mizer/size_structured_population', 'parameters': mizer_params, 'coupling': mizer_coupling, 'initialization': mizer_initialization } fabm_yaml = {'instances': {'fish': mizer_yaml}} if not isinstance(prey, BasePreyCollection): prey = PreyCollection(*prey) self.prey = prey iprey = 0 for name, mass in zip(self.prey.names, self.prey.masses): iprey += 1 fabm_yaml['instances'][name] = { 'model': 'mizer/prey', 'parameters': { 'w': float(mass) } } mizer_coupling['Nw_prey%i' % iprey] = '%s/Nw' % name mizer_params['nprey'] = iprey with fabm_yaml_file: yaml.dump(fabm_yaml, fabm_yaml_file, default_flow_style=False) self.fabm_model = pyfabm.Model(fabm_yaml_path) assert not pyfabm.hasError( ), 'pyfabm library failed during initialization' self.prey_indices = numpy.empty((iprey, ), dtype=int) for iprey, name in enumerate(self.prey.names): for self.prey_indices[iprey], variable in enumerate( self.fabm_model.state_variables): if variable.path == '%s/Nw' % name: break else: assert False, 'Prey %s/Nw not found in model.' % name self.bin_indices = [] self.bin_masses = [] while 1: for i, variable in enumerate(self.fabm_model.state_variables): if variable.path == 'fish/Nw%i' % (len(self.bin_indices) + 1, ): self.bin_masses.append( variable.getRealProperty('particle_mass')) break else: break self.bin_indices.append(i) self.bin_masses = numpy.array(self.bin_masses) log10masses = numpy.log10(self.bin_masses) self.log10bin_width = log10masses[1] - log10masses[ 0] # assume equal log10 spacing between mizer size classes self.bin_widths = 10.**(log10masses + self.log10bin_width / 2) - 10.**( log10masses - self.log10bin_width / 2) # Used to convert between depth-integrated fluxes and depth-explicit fluxes (not used if prey is prescribed) self.fabm_model.cell_thickness = 1. try: self.fabm_model.findDependency('bottom_depth').value = 1. except KeyError: pass if temperature is not None: self.temperature = self.fabm_model.findDependency('temperature') self.temperature.value = self.temperature_provider.mean() assert self.temperature.value > -10. and self.temperature.value < 40., 'Invalid temperature mean (%s)' % self.temperature.value if depth is not None: self.interaction_depth = self.fabm_model.findDependency( 'fish/interaction_depth') self.interaction_depth.value = self.depth_provider.mean() if verbose: print('Mean depth: %.1f m' % (self.interaction_depth.value, )) # Verify the model is ready to be used assert self.fabm_model.checkReady( ), 'One or more model dependencies have not been fulfilled.' if verbose: for parameter in self.fabm_model.parameters: if parameter.path.startswith('fish/'): print('%s: %s %s' % (parameter.long_name, parameter.value, parameter.units)) self.initial_state = numpy.copy(self.fabm_model.state) self.recruitment_from_prey = recruitment_from_prey
parser = argparse.ArgumentParser(description='This script verifies that a biogeochemical model returns valid derivatives under a wide variety of inputs (state variables, environmental dependencies). Different tests can be run, using either random values or extremes for inputs, and running randomized or exhaustive tests.') parser.add_argument('model_path',help='Path to a YAML file with the model configuration (typically fabm.yaml)') parser.add_argument('ranges_path',help='Path to a YAML file with ranges for all model inputs (state variables, environmental dependencies)') parser.add_argument('--test',type=str,choices=('randomized','extremes_per_variable','extremes_randomized','extremes_all'),action='append',help='Path to a YAML file with ranges for all model inputs (state variables, environmental dependencies)') parser.add_argument('--write-ranges',action='store_true',help='Write ranges file with default variable ranges',default=False) args = parser.parse_args() try: import pyfabm except ImportError: print 'Unable to load pyfabm. See https://github.com/fabm-model/code/wiki/python.' sys.exit(1) # Create model object from YAML file. model = pyfabm.Model(args.model_path) if args.write_ranges: with open(args.ranges_path,'w') as f: def writeRanges(variables): for variable in variables: f.write('%s: [0,%s]\n' % (variable.name,10*variable.value)) writeRanges(model.state_variables) writeRanges(model.dependencies) print 'Default ranges have been written to %s.' % args.ranges_path sys.exit(0) with open(args.ranges_path,'rU') as f: ranges = yaml.load(f) if not isinstance(ranges,dict): print 'Range file %s should contain a dicionary mapping each variable to its range (or constant value).' % args.ranges_path
def main(): """ Run pyfabm-ERSEM tutorial :param model_path: Full path to netCDF model output :type model_path: str :return: list of oxygen saturation concentrations :rtype: list """ dir_path = os.path.dirname(os.path.realpath(__file__)) ersem_dir = os.path.dirname(os.path.dirname(dir_path)) # Path to ERSEM yaml file ersem_yaml_file = os.path.join( ersem_dir, 'testcases', 'fabm-ersem-15.06-L4-noben-docdyn-iop.yaml') if not os.path.isfile(ersem_yaml_file): raise RuntimeError("Could not find Ersem yaml file with the " "{}".format(ersem_yaml_file)) # Create model model = pyfabm.Model(ersem_yaml_file) # Configure the environment model.findDependency('longitude').value = -4.15 model.findDependency('latitude').value = 50.25 model.findDependency('number_of_days_since_start_of_the_year').value = 0. model.findDependency('temperature').value = 10. model.findDependency('wind_speed').value = 1. model.findDependency('surface_downwelling_shortwave_flux').value = 50. model.findDependency('practical_salinity').value = 35. model.findDependency('pressure').value = 10. model.findDependency('density').value = 1035. model.findDependency('mole_fraction_of_carbon_dioxide_in_air').value = 280. model.findDependency('absorption_of_silt').value = 0.07 model.findDependency('bottom_stress').value = 0. model.findDependency('cell_thickness').value = 1. model.setCellThickness(1) # Verify the model is ready to be used assert model.checkReady( ), 'One or more model dependencies have not been fulfilled.' # Define ranges over which temperature and salinity will be varied n_points = 50 temperature_array = np.linspace(5, 35, n_points) salinity_array = np.linspace(25, 45, n_points) # Create an array in which to store oxygen_saturation_concentrations oxygen_saturation_concentration = np.empty((n_points, n_points), dtype=float) # Calculate oxygen saturation concentrations using ERSEM for t_idx, t in enumerate(temperature_array): model.findDependency('temperature').value = t for s_idx, s in enumerate(salinity_array): model.findDependency('practical_salinity').value = s _ = model.getRates() oxygen_saturation_concentration[t_idx, s_idx] = \ model.findDiagnosticVariable('O2/osat').value # Create the figure figure = plt.figure() axes = plt.gca() # Set color map cmap = cm.get_cmap('YlOrRd') # Plot plot = axes.pcolormesh(temperature_array, salinity_array, oxygen_saturation_concentration, shading='auto', cmap=cmap) axes.set_xlabel('Temperature (deg. C)') axes.set_ylabel('Salinity (psu)') # Add colour bar cbar = figure.colorbar(plot) cbar.set_label('O$_{2}$ (mmol m$^{-3}$)') plt.show() return oxygen_saturation_concentration