Adding a new derived variable
Every derived variable is implemented as a subclass of MDerivedDataFieldProcessor.
Each processor declares the CF standard name of the output variable,
the list of required input variable names (optionally qualified with a level type),
and a compute() method that fills the output grid.
All processors are registered in MDerivedMetVarsDataSource::MDerivedMetVarsDataSource()
in src/data/derivedvars/derivedmetvarsdatasource.cpp.
For a list of existing derived variables and user-facing configuration, see Derived Variables.
Adding a C++ derived variable
Subclass
MDerivedDataFieldProcessorinsrc/data/derivedvars/derivedmetvars_standard.h/.cpp(or a new file):class MMyNewVariableProcessor : public MDerivedDataFieldProcessor { public: MMyNewVariableProcessor(); void compute(const QList<MDataLease<MStructuredGrid>>& inputGrids, MStructuredGrid *derivedGrid) override; };
Implement the constructor to declare the standard name and required inputs:
MMyNewVariableProcessor::MMyNewVariableProcessor() : MDerivedDataFieldProcessor( "my_new_variable", QStringList() << "air_temperature" << "specific_humidity") {}
To restrict to a specific level type, append a qualifier to the input name and pass allowed output level types as a third argument:
MMyNewVariableProcessor::MMyNewVariableProcessor() : MDerivedDataFieldProcessor( "my_new_variable", QStringList() << "eastward_wind/HYBRID_SIGMA_PRESSURE_3D" << "surface_air_pressure/SURFACE_2D", {HYBRID_SIGMA_PRESSURE_3D}) {}
Implement
compute(). TheinputGridslist matches the declared input order. Write results toderivedGrid:void MMyNewVariableProcessor::compute( const QList<MDataLease<MStructuredGrid>>& inputGrids, MStructuredGrid *derivedGrid) { const auto& tempGrid = inputGrids.at(0); const auto& humGrid = inputGrids.at(1); for (int k = 0; k < derivedGrid->getNumLevels(); ++k) { for (int j = 0; j < derivedGrid->getNumLats(); ++j) { for (int i = 0; i < derivedGrid->getNumLons(); ++i) { float value = 0.f; /* your computation here... */ derivedGrid->setValue(k, j, i, value); } } } }
Register the processor in
MDerivedMetVarsDataSource::MDerivedMetVarsDataSource()inderivedmetvarsdatasource.cpp:registerDerivedDataFieldProcessor(new MMyNewVariableProcessor());
Adding a Python-based derived variable
Python-derived variables call MetPy (or arbitrary Python code) through Met.3D’s Python
interface. Data is exchanged as an xarray.Dataset via an in-memory NetCDF buffer.
Add an endpoint type. In
src/data/derivedvars/pythonderivedprocessor.h, add a value to theEndpointTypeenum beforeINVALID:enum EndpointType { METPY_PV_BAROCLINIC = 0, // ... existing values ... MY_NEW_ENDPOINT = N, // next available index INVALID };
Expose the value to Python. In
src/system/mpyinterfacebindings.cpp, add a.value()line to the pybind11 enum block:.value("MY_NEW_ENDPOINT", PyEndpoint::MY_NEW_ENDPOINT)
Register the processor. In
MPythonDerivedProcessor::registerProcessorsTo()inpythonderivedprocessor.cpp:auto myProcessor = new MPythonDerivedProcessor( "my_new_variable_metpy", {"air_pressure", "air_temperature"}, // required inputs, in order PyEndpoint::MY_NEW_ENDPOINT); dataSource->registerDerivedDataFieldProcessor(myProcessor);
Add the Python function. In
src/python/interface/python_endpoints.py:def my_new_variable(dataset, in_grid_names, out_grid_name): import metpy.calc as mpcalc from metpy.units import units p = dataset[in_grid_names[0]] * units('Pa') t = dataset[in_grid_names[1]] * units('K') dataset[out_grid_name] = mpcalc.some_function(p, t) return dataset
Dispatch the function. In
invoke_python_endpoint()inpython_endpoints.py:elif func == derived_var_bindings.EndpointType.MY_NEW_ENDPOINT: return my_new_variable(dataset, in_grid_names, out_grid_name)
Note
If you have implemented a derived variable that you think would be useful to other Met.3D users, we would be happy to include it in the main codebase. Please consider submitting a merge request on GitLab.