diff --git a/alg/CMakeLists.txt b/alg/CMakeLists.txt index 07e1af693..8d3063453 100644 --- a/alg/CMakeLists.txt +++ b/alg/CMakeLists.txt @@ -28,6 +28,7 @@ set(teca_alg_cxx_srcs teca_evaluate_expression.cxx teca_face_to_cell_centering.cxx teca_geography.cxx + teca_gradient.cxx teca_indexed_dataset_cache.cxx teca_integrated_vapor_transport.cxx teca_integrated_water_vapor.cxx diff --git a/alg/teca_gradient.cxx b/alg/teca_gradient.cxx new file mode 100644 index 000000000..9c5b1735f --- /dev/null +++ b/alg/teca_gradient.cxx @@ -0,0 +1,414 @@ +#include "teca_gradient.h" + +#include "teca_cartesian_mesh.h" +#include "teca_array_collection.h" +#include "teca_variant_array.h" +#include "teca_variant_array_impl.h" +#include "teca_metadata.h" + +#include <algorithm> +#include <iostream> +#include <string> +#include <cmath> + +#if defined(TECA_HAS_BOOST) +#include <boost/program_options.hpp> +#endif + +using std::string; +using std::vector; +using std::cerr; +using std::endl; +using std::cos; + +//#define TECA_DEBUG + +namespace { + +template <typename num_t> +constexpr num_t deg_to_rad() { return num_t(M_PI)/num_t(180); } + +template <typename num_t> +constexpr num_t earth_radius() { return num_t(6371.0e3); } + +// compute the gradient using first-order, centered finite difference +// assumes fixed mesh spacing. here we add periodic bc in lon +// and apply unit stride vector optimization strategy to loops +template <typename num_t, typename pt_t> +void gradient(num_t *grad_x, num_t *grad_y, const pt_t *lon, const pt_t *lat, + const num_t *f, unsigned long n_lon, + unsigned long n_lat, bool periodic_lon=true) +{ + size_t n_bytes = n_lat*sizeof(num_t); + num_t *delta_x = static_cast<num_t*>(malloc(n_bytes)); + + // delta lon as a function of latitude + num_t delta_x_equator = num_t(2)*(lon[1] - lon[0]) * deg_to_rad<num_t>() * earth_radius<num_t>(); + for (unsigned long j = 0; j < n_lat; ++j) + delta_x[j] = delta_x_equator * cos(lat[j] * deg_to_rad<num_t>()); + + // delta lat + num_t delta_y = (lat[1] - lat[0]) * deg_to_rad<num_t>() * earth_radius<num_t>(); + num_t dy = num_t(2)*delta_y; + + unsigned long max_i = n_lon - 1; + unsigned long max_j = n_lat - 1; + + // gradient + for (unsigned long j = 1; j < max_j; ++j) + { + unsigned long jj = j*n_lon; + const num_t *f_x2 = f + jj + n_lon; + const num_t *f_x0 = f + jj - n_lon; + const num_t *f_y2 = f + jj + 1; + const num_t *f_y0 = f + jj - 1; + num_t *gx = grad_x + jj; + num_t *gy = grad_y + jj; + num_t dx = delta_x[j]; + + for (unsigned long i = 1; i < max_i; ++i) + { + gx[i] = (f_x2[i] - f_x0[i]) / dx; + gy[i] = (f_y2[i] - f_y0[i]) / dy; + } + } + + if (periodic_lon) + { + // periodic in longitude + for (unsigned long j = 1; j < max_j; ++j) + { + unsigned long jj = j*n_lon; + const num_t *f_x2 = f + jj + n_lon; + const num_t *f_x0 = f + jj - n_lon; + const num_t *f_y2 = f + jj + 1; + const num_t *f_y0 = f + jj + max_i; + num_t *gx = grad_x + jj; + num_t *gy = grad_y + jj; + num_t dx = delta_x[j]; + + gx[0] = (f_x2[0] - f_x0[0]) / dx; + gy[0] = (f_y2[0] - f_y0[0]) / dx; + } + + for (unsigned long j = 1; j < max_j; ++j) + { + unsigned long jj = j*n_lon; + const num_t *f_x2 = f + jj + max_i + n_lon; + const num_t *f_x0 = f + jj + max_i - n_lon; + const num_t *f_y2 = f + jj; + const num_t *f_y0 = f + jj + max_i - 1; + num_t *gx = grad_x + jj + max_i; + num_t *gy = grad_y + jj + max_i; + num_t dx = delta_x[j]; + + gx[0] = (f_x2[0] - f_x0[0]) / dx; + gy[0] = (f_y2[0] - f_y0[0]) / dx; + } + } + else + { + // zero it out + for (unsigned long j = 1; j < max_j; ++j) + { + grad_x[j*n_lon] = num_t(); + grad_y[j*n_lon] = num_t(); + } + + for (unsigned long j = 1; j < max_j; ++j) + { + grad_x[j*n_lon + max_i] = num_t(); + grad_y[j*n_lon + max_i] = num_t(); + } + } + + // extend values into lat boundaries + num_t *dest = grad_x; + num_t *src = grad_x + n_lon; + for (unsigned long i = 0; i < n_lon; ++i) + dest[i] = src[i+n_lon]; + dest = grad_y; + src = grad_y + n_lon; + for (unsigned long i = 0; i < n_lon; ++i) + dest[i] = src[i+n_lon]; + + dest = grad_x + max_j*n_lon; + src = dest - n_lon; + for (unsigned long i = 0; i < n_lon; ++i) + dest[i] = src[i]; + dest = grad_y + max_j*n_lon; + src = dest - n_lon; + for (unsigned long i = 0; i < n_lon; ++i) + dest[i] = src[i]; + + free(delta_x); + + return; +} +}; + + +// -------------------------------------------------------------------------- +teca_gradient::teca_gradient() : + scalar_field(), + gradient_field_x("gradient_x"), + gradient_field_y("gradient_y") +{ + this->set_number_of_input_connections(1); + this->set_number_of_output_ports(1); +} + +// -------------------------------------------------------------------------- +teca_gradient::~teca_gradient() +{} + +#if defined(TECA_HAS_BOOST) +// -------------------------------------------------------------------------- +void teca_gradient::get_properties_description( + const string &prefix, options_description &global_opts) +{ + options_description opts("Options for " + + (prefix.empty()?"teca_gradient":prefix)); + + opts.add_options() + TECA_POPTS_GET(std::string, prefix, scalar_field, + "array containing the scalar field") + TECA_POPTS_GET(std::string, prefix, gradient_field_x, + "array to store the x-direction of the computed gradient") + TECA_POPTS_GET(std::string, prefix, gradient_field_y, + "array to store the y-direction of the computed gradient") + ; + + this->teca_algorithm::get_properties_description(prefix, opts); + + global_opts.add(opts); +} + +// -------------------------------------------------------------------------- +void teca_gradient::set_properties( + const string &prefix, variables_map &opts) +{ + this->teca_algorithm::set_properties(prefix, opts); + + TECA_POPTS_SET(opts, std::string, prefix, scalar_field) + TECA_POPTS_SET(opts, std::string, prefix, gradient_field_x) + TECA_POPTS_SET(opts, std::string, prefix, gradient_field_y) +} +#endif + +// -------------------------------------------------------------------------- +std::string teca_gradient::get_scalar_field( + const teca_metadata &request) +{ + std::string scalar_var = this->scalar_field; + + if (scalar_var.empty() && + request.has("teca_gradient::scalar_field")) + request.get("teca_gradient::scalar_field", scalar_var); + + return scalar_var; +} + +// -------------------------------------------------------------------------- +std::string teca_gradient::get_gradient_field_x( + const teca_metadata &request) +{ + std::string grad_x_var = this->gradient_field_x; + + if (grad_x_var.empty() && + request.has("teca_gradient::gradient_field_x")) + request.get("teca_gradient::gradient_field_y", grad_x_var); + + return grad_x_var; +} + +// -------------------------------------------------------------------------- +std::string teca_gradient::get_gradient_field_y( + const teca_metadata &request) +{ + std::string grad_y_var = this->gradient_field_y; + + if (grad_y_var.empty() && + request.has("teca_gradient::gradient_field_y")) + request.get("teca_gradient::gradient_field_y", grad_y_var); + + return grad_y_var; +} + + +// -------------------------------------------------------------------------- +teca_metadata teca_gradient::get_output_metadata( + unsigned int port, + const std::vector<teca_metadata> &input_md) +{ +#ifdef TECA_DEBUG + cerr << teca_parallel_id() + << "teca_gradient::get_output_metadata" << endl; +#endif + (void)port; + + // add in the arrays we will generate + teca_metadata out_md(input_md[0]); + out_md.append("variables", this->gradient_field_x); + out_md.append("variables", this->gradient_field_y); + + return out_md; +} + +// -------------------------------------------------------------------------- +std::vector<teca_metadata> teca_gradient::get_upstream_request( + unsigned int port, + const std::vector<teca_metadata> &input_md, + const teca_metadata &request) +{ + (void)port; + (void)input_md; + + vector<teca_metadata> up_reqs; + + // get the name of the arrays we need to request + std::string scalar_var = this->get_scalar_field(request); + if (scalar_var.empty()) + { + TECA_FATAL_ERROR("scalar field array was not specified") + return up_reqs; + } + + std::string grad_x_var = this->get_gradient_field_x(request); + if (grad_x_var.empty()) + { + TECA_FATAL_ERROR("gradient x array name was not specified") + return up_reqs; + } + + std::string grad_y_var = this->get_gradient_field_y(request); + if (grad_y_var.empty()) + { + TECA_FATAL_ERROR("gradient y array name was not specified") + return up_reqs; + } + + + // copy the incoming request to preserve the downstream + // requirements and add the arrays we need + teca_metadata req(request); + + std::set<std::string> arrays; + if (req.has("arrays")) + req.get("arrays", arrays); + + arrays.insert(this->scalar_field); + + // capture the array we produce + arrays.erase(this->get_gradient_field_x(request)); + arrays.erase(this->get_gradient_field_y(request)); + + // update the request + req.set("arrays", arrays); + + // send it up + up_reqs.push_back(req); + return up_reqs; +} + +// -------------------------------------------------------------------------- +const_p_teca_dataset teca_gradient::execute( + unsigned int port, + const std::vector<const_p_teca_dataset> &input_data, + const teca_metadata &request) +{ +#ifdef TECA_DEBUG + cerr << teca_parallel_id() + << "teca_gradient::execute" << endl; +#endif + (void)port; + + // get the input mesh + const_p_teca_cartesian_mesh in_mesh + = std::dynamic_pointer_cast<const teca_cartesian_mesh>(input_data[0]); + + if (!in_mesh) + { + TECA_FATAL_ERROR("teca_cartesian_mesh is required") + return nullptr; + } + + // get the scalar field array + std::string scalar_var = this->get_scalar_field(request); + + if (scalar_var.empty()) + { + TECA_FATAL_ERROR("scalar_field was not specified") + return nullptr; + } + + const_p_teca_variant_array scalar + = in_mesh->get_point_arrays()->get(scalar_var); + + if (!scalar) + { + TECA_FATAL_ERROR("requested array \"" << scalar_var << "\" not present.") + return nullptr; + } + + // get the input coordinate arrays + const_p_teca_variant_array lon = in_mesh->get_x_coordinates(); + const_p_teca_variant_array lat = in_mesh->get_y_coordinates(); + + if (!lon || !lat) + { + TECA_FATAL_ERROR("lat lon mesh coordinates not present.") + return nullptr; + } + + // allocate the output arrays + p_teca_variant_array grad_x = scalar->new_instance(); + p_teca_variant_array grad_y = scalar->new_instance(); + grad_x->resize(scalar->size()); + grad_y->resize(scalar->size()); + + // compute gradient + NESTED_TEMPLATE_DISPATCH_FP( + const teca_variant_array_impl, + lon.get(), 1, + + auto sp_lon = dynamic_cast<TT1*>(lon.get())->get_cpu_accessible(); + const NT1 *p_lon = sp_lon.get(); + + auto sp_lat = dynamic_cast<TT1*>(lat.get())->get_cpu_accessible(); + const NT1 *p_lat = sp_lat.get(); + + NESTED_TEMPLATE_DISPATCH_FP( + teca_variant_array_impl, + grad_x.get(), 2, + + auto sp_scalar = dynamic_cast<const TT2*>(scalar.get())->get_cpu_accessible(); + const NT2 *p_scalar = sp_scalar.get(); + + auto sp_grad_x = dynamic_cast<TT2*>(grad_x.get())->get_cpu_accessible(); + NT2 *p_grad_x = sp_grad_x.get(); + + auto sp_grad_y = dynamic_cast<TT2*>(grad_y.get())->get_cpu_accessible(); + NT2 *p_grad_y = sp_grad_y.get(); + + + ::gradient(p_grad_x, p_grad_y, p_lon, p_lat, + p_scalar, lon->size(), lat->size()); + ) + ) + + // create the output mesh, pass everything through, and + // add the gradient array + p_teca_cartesian_mesh out_mesh = teca_cartesian_mesh::New(); + + out_mesh->shallow_copy( + std::const_pointer_cast<teca_cartesian_mesh>(in_mesh)); + + out_mesh->get_point_arrays()->append( + this->get_gradient_field_x(request), grad_x); + + out_mesh->get_point_arrays()->append( + this->get_gradient_field_y(request), grad_y); + + return out_mesh; +} diff --git a/alg/teca_gradient.h b/alg/teca_gradient.h new file mode 100644 index 000000000..bf3c4e931 --- /dev/null +++ b/alg/teca_gradient.h @@ -0,0 +1,82 @@ +#ifndef teca_gradient_h +#define teca_gradient_h + +#include "teca_shared_object.h" +#include "teca_algorithm.h" +#include "teca_metadata.h" + +#include <string> +#include <vector> + +TECA_SHARED_OBJECT_FORWARD_DECL(teca_gradient) + +/// An algorithm that computes gradient from a vector field. +class TECA_EXPORT teca_gradient : public teca_algorithm +{ +public: + TECA_ALGORITHM_STATIC_NEW(teca_gradient) + TECA_ALGORITHM_DELETE_COPY_ASSIGN(teca_gradient) + TECA_ALGORITHM_CLASS_NAME(teca_gradient) + ~teca_gradient(); + + // report/initialize to/from Boost program options + // objects. + TECA_GET_ALGORITHM_PROPERTIES_DESCRIPTION() + TECA_SET_ALGORITHM_PROPERTIES() + + /** @name scalar field + * set the array that contains the scalar field to compute gradient + * from. + */ + ///@{ + TECA_ALGORITHM_PROPERTY(std::string, scalar_field) + ///@} + + /** @name gradient_field_x + * set the name of the array to store the x-component of the + * gradient in. + */ + ///@{ + TECA_ALGORITHM_PROPERTY(std::string, gradient_field_x) + ///@} + + /** @name gradient_field_y + * set the name of the array to store the y-component of the + * gradient in. + */ + ///@{ + TECA_ALGORITHM_PROPERTY(std::string, gradient_field_y) + ///@} + + +protected: + teca_gradient(); + + std::string get_scalar_field(const teca_metadata &request); + std::string get_gradient_field_x(const teca_metadata &request); + std::string get_gradient_field_y(const teca_metadata &request); + +private: + using teca_algorithm::get_output_metadata; + + teca_metadata get_output_metadata( + unsigned int port, + const std::vector<teca_metadata> &input_md) override; + + std::vector<teca_metadata> get_upstream_request( + unsigned int port, + const std::vector<teca_metadata> &input_md, + const teca_metadata &request) override; + + const_p_teca_dataset execute( + unsigned int port, + const std::vector<const_p_teca_dataset> &input_data, + const teca_metadata &request) override; + +private: + std::string scalar_field; + std::string gradient_field_x; + std::string gradient_field_y; +}; + +#endif