def initialize_realizations(self, rlz_callbacks=None): """ Create records for the `hzrdr.lt_realization` and `htemp.source_progress` records. This function works either in random sampling mode (when lt_realization models get the random seed value) or in enumeration mode (when weight values are populated). In both cases we record the logic tree paths for both trees in the `lt_realization` record, as well as ordinal number of the realization (zero-based). Then we create `htemp.source_progress` records for each source in the source model chosen for each realization, see :meth:`initialize_source_progress`. :param rlz_callbacks: Optionally, you can specify a list of callbacks for each realization. In the case of the classical hazard calculator, for example, we would include a callback function to create initial records for temporary hazard curve result data. Callbacks should accept a single argument: A :class:`~openquake.db.models.LtRealization` object. """ logs.log_progress("initializing realizations", 2) if self.job.hazard_calculation.number_of_logic_tree_samples > 0: # random sampling of paths self._initialize_realizations_montecarlo( rlz_callbacks=rlz_callbacks) else: # full paths enumeration self._initialize_realizations_enumeration( rlz_callbacks=rlz_callbacks)
def execute(self): """ Calculation work is parallelized over sources, which means that each task will compute hazard for all sites but only with a subset of the seismic sources defined in the input model. The general workflow is as follows: 1. Fill the queue with an initial set of tasks. The number of initial tasks is configurable using the `concurrent_tasks` parameter in the `[hazard]` section of the OpenQuake config file. 2. Wait for tasks to signal completion (via AMQP message) and enqueue a new task each time another completes. Once all of the job work is enqueued, we just wait until all of the tasks conclude. """ block_size = int(config.get('hazard', 'block_size')) concurrent_tasks = int(config.get('hazard', 'concurrent_tasks')) self.progress = dict(total=0, computed=0) # The following two counters are in a dict so that we can use them in # the closures below. # When `self.progress['compute']` becomes equal to # `self.progress['total']`, # `execute` can conclude. task_gen = self.task_arg_gen(block_size) exchange, conn_args = exchange_and_conn_args() routing_key = ROUTING_KEY_FMT % dict(job_id=self.job.id) task_signal_queue = kombu.Queue( 'htasks.job.%s' % self.job.id, exchange=exchange, routing_key=routing_key, durable=False, auto_delete=True) with kombu.BrokerConnection(**conn_args) as conn: task_signal_queue(conn.channel()).declare() with conn.Consumer( task_signal_queue, callbacks=[self.get_task_complete_callback(task_gen)]): # First: Queue up the initial tasks. for _ in xrange(concurrent_tasks): try: self.core_calc_task.apply_async(task_gen.next()) except StopIteration: # If we get a `StopIteration` here, that means we have # a number of tasks < concurrent_tasks. # This basically just means that we could be # under-utilizing worker node resources. break while (self.progress['computed'] < self.progress['total']): # This blocks until a message is received. # Once we receive a completion signal, enqueue the next # piece of work (if there's anything left to be done). # (The `task_complete_callback` will handle additional # queuing.) conn.drain_events() logs.log_progress("hazard calculation 100% complete", 2)
def _switch_to_job_phase(job, status): """Switch to a particular phase of execution. This involves setting the job's status as well as creating a `job_phase_stats` record. :param job: An :class:`~openquake.db.models.OqJob` instance. :param str status: one of the following: pre_executing, executing, post_executing, post_processing, export, clean_up, complete """ job.status = status job.save() models.JobPhaseStats.objects.create(oq_job=job, job_status=status) logs.log_progress("%s" % status, 1)
def initialize_sources(self): """ Parse and validation logic trees (source and gsim). Then get all sources referenced in the the source model logic tree, create :class:`~openquake.db.models.Input` records for all of them, parse then, and save the parsed sources to the `parsed_source` table (see :class:`openquake.db.models.ParsedSource`). """ logs.log_progress("initializing sources", 2) hc = self.job.hazard_calculation [smlt] = models.inputs4hcalc(hc.id, input_type='lt_source') [gsimlt] = models.inputs4hcalc(hc.id, input_type='lt_gsim') source_paths = logictree.read_logic_trees( hc.base_path, smlt.path, gsimlt.path) src_inputs = [] for src_path in source_paths: full_path = os.path.join(hc.base_path, src_path) # Get or reuse the 'source' Input: inp = engine2.get_input( full_path, 'source', hc.owner, hc.force_inputs) src_inputs.append(inp) # Associate the source input to the calculation: models.Input2hcalc.objects.get_or_create( input=inp, hazard_calculation=hc) # Associate the source input to the source model logic tree input: models.Src2ltsrc.objects.get_or_create( hzrd_src=inp, lt_src=smlt, filename=src_path) # Now parse the source models and store `pared_source` records: for src_inp in src_inputs: src_content = StringIO.StringIO(src_inp.model_content.raw_content) sm_parser = nrml_parsers.SourceModelParser(src_content) src_db_writer = source.SourceDBWriter( src_inp, sm_parser.parse(), hc.rupture_mesh_spacing, hc.width_of_mfd_bin, hc.area_source_discretization) src_db_writer.serialize()
def initialize_site_model(self): """ If a site model is specified in the calculation configuration. parse it and load it into the `hzrdi.site_model` table. This includes a validation step to ensure that the area covered by the site model completely envelops the calculation geometry. (If this requirement is not satisfied, an exception will be raised. See :func:`openquake.calculators.hazard.general.validate_site_model`.) Then, take all of the points/locations of interest defined by the calculation geometry. For each point, do distance queries on the site model and get the site parameters which are closest to the point of interest. This aggregation of points to the closest site parameters is what we store in `htemp.site_data`. (Computing this once prior to starting the calculation is optimal, since each task will need to consider all sites.) """ logs.log_progress("initializing site model", 2) hc_id = self.job.hazard_calculation.id site_model_inp = get_site_model(hc_id) if site_model_inp is not None: # Explicit cast to `str` here because the XML parser doesn't like # unicode. (More specifically, lxml doesn't like unicode.) site_model_content = str(site_model_inp.model_content.raw_content) # Store `site_model` records: store_site_model( site_model_inp, StringIO.StringIO(site_model_content)) mesh = self.job.hazard_calculation.points_to_compute() # Get the site model records we stored: site_model_data = models.SiteModel.objects.filter( input=site_model_inp) validate_site_model(site_model_data, mesh) store_site_data(hc_id, site_model_inp, mesh)
def _switch_to_job_phase(job_ctxt, ctype, status): """Switch to a particular phase of execution. This involves creating a `job_phase_stats` record and logging the new status. :param job_ctxt: An :class:`~openquake.engine.JobContext` instance. :param str ctype: calculation type (hazard|risk) :param str status: one of the following: pre_executing, executing, post_executing, post_processing, export, clean_up, complete """ job = OqJob.objects.get(id=job_ctxt.job_id) JobPhaseStats.objects.create(oq_job=job, ctype=ctype, job_status=status) logs.log_progress("%s (%s)" % (status, ctype), 1) if status == "executing": # Record the compute nodes that were available at the beginning of the # execute phase so we can detect failed nodes later. failed_nodes = monitor.count_failed_nodes(job) if failed_nodes == -1: logs.LOG.critical("No live compute nodes, aborting calculation") sys.exit(1)
def curves2nrml(target_dir, job): """Write hazard curves to NRML files. :param str target_dir: where should the output files go? :param int job_id: the database key of the job at hand. """ LOG.debug("> curves2nrml") hc_outputs = models.Output.objects.filter(oq_job=job, output_type="hazard_curve") for hc_output in hc_outputs: export_hazard_curves(hc_output, target_dir) hco_count = len(hc_outputs) if hco_count > 1: logs.log_progress( "%s hazard curves exported to %s" % (hco_count, target_dir), 2) elif hco_count == 1: logs.log_progress("One hazard curve exported to %s" % target_dir, 2) else: logs.log_progress("No hazard curves found for export", 2) LOG.debug("< curves2nrml")
def execute(self): """ Calculation work is parallelized over sources, which means that each task will compute hazard for all sites but only with a subset of the seismic sources defined in the input model. The general workflow is as follows: 1. Fill the queue with an initial set of tasks. The number of initial tasks is configurable using the `concurrent_tasks` parameter in the `[hazard]` section of the OpenQuake config file. 2. Wait for tasks to signal completion (via AMQP message) and enqueue a new task each time another completes. Once all of the job work is enqueued, we just wait until all of the tasks conclude. """ job = self.job hc = job.hazard_calculation sources_per_task = int(config.get('hazard', 'block_size')) concurrent_tasks = int(config.get('hazard', 'concurrent_tasks')) progress = dict(total=0, computed=0) # The following two counters are in a dict so that we can use them in # the closures below. # When `progress['compute']` becomes equal to `progress['total']`, # `execute` can conclude. task_gen = self.task_arg_gen(hc, job, sources_per_task, progress) def task_complete_callback(body, message): """ :param dict body: ``body`` is the message sent by the task. The dict should contain 2 keys: `job_id` and `num_sources` (to indicate the number of sources computed). Both values are `int`. :param message: A :class:`kombu.transport.pyamqplib.Message`, which contains metadata about the message (including content type, channel, etc.). See kombu docs for more details. """ job_id = body['job_id'] num_sources = body['num_sources'] assert job_id == job.id progress['computed'] += num_sources logs.log_percent_complete(job_id, "hazard") # Once we receive a completion signal, enqueue the next # piece of work (if there's anything left to be done). try: self.core_calc_task.apply_async(task_gen.next()) except StopIteration: # There are no more tasks to dispatch; now we just need # to wait until all tasks signal completion. pass message.ack() exchange, conn_args = exchange_and_conn_args() routing_key = ROUTING_KEY_FMT % dict(job_id=job.id) task_signal_queue = kombu.Queue( 'htasks.job.%s' % job.id, exchange=exchange, routing_key=routing_key, durable=False, auto_delete=True) with kombu.BrokerConnection(**conn_args) as conn: task_signal_queue(conn.channel()).declare() with conn.Consumer(task_signal_queue, callbacks=[task_complete_callback]): # First: Queue up the initial tasks. for _ in xrange(concurrent_tasks): try: self.core_calc_task.apply_async(task_gen.next()) except StopIteration: # If we get a `StopIteration` here, that means we have # a number of tasks < concurrent_tasks. # This basically just means that we could be # under-utilizing worker node resources. break while (progress['computed'] < progress['total']): # This blocks until a message is received. # Once we receive a completion signal, enqueue the next # piece of work (if there's anything left to be done). # (The `task_complete_callback` will handle additional # queuing.) conn.drain_events() logs.log_progress("hazard calculation 100% complete", 2)