From 97af221efbcb89c342b86a0b84bb3c16c4f7467c Mon Sep 17 00:00:00 2001 From: jdcpni Date: Tue, 16 Mar 2021 15:06:51 -0400 Subject: [PATCH] Feat/report/params (#1953) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * • composition.py - run(): use rich.Progress for looping through trials * - * - * - * - * - * - * - * - * - * - * - * • composition.py - import placed at top * - * • composition.py - run(): added support for indeterminate progress using rich * • composition.py - run(): clean-up of rich.progress code * • composition.py - run(): clean-up of rich.progress code * - * • composition.py - run(): added support for indeterminate progress using rich * • composition.py - run(): simulation progress now working properly * • composition.py - run(): added show_progress and show_output args - execute(): added show_output arg * • composition.py - run(): modified example in controlsignal.py docstring to pass test * • composition.py - run(): modified example in controlsignal.py docstring to pass test * - * • Pulled from current devel * • composition.py: bug fixes * - * • composition.py - last commit before implementing PNLprogress * • composition.py - first draft using PNLProgress and _report_output * • composition.py - first draft using PNLProgress and _report_output * - * - * - * - * • PNLProgress working * - * • PNLProgress: elaborated for future support of pnl_view * - * • progress.py: added support for showing simulations * • progress.py: added support for showing simulations * - * - * - * • Fixed bug causing crash in test_nested_composition_run * • Fixed bug causing crash in test_nested_composition_run * - * • Codestyle fixes * • progress.py: - fixed simulation progress reporting - should consolidate all executions in a given simualation to a single task • composition.py: - limited progress reporting to execute() (no longer in run) • autodiffcomposition.py: - execute: added progress reporting - still need to handle output report * - * - * - * • Merged with devel * - * • Merged with devel * - * - * - * - * - * - * • progress.py: working for nested sims * - * - * - * - * - * • composition.py: docstring updated for output and progress reporting * • progress.py - bug fix • composition.py - change show_output default to False * • progress.py - bug fix * • progress.py - patch trial_num reporting bug (with hack) * • progress.py: before switch from rich_report to full_report * - * - * - * • Progress: before modifying PNLProgers._use_rich * • Progress: implemented CAPTURE option for rich output * - * - * • Preferences.rst - pointed to PreferenceSet.py • preferenceset.py - created docstring to describe Standard Preferences * - * - * • tests - composition: added test_progress.py with unit tests for PNLProgress • composition.py - execute(): modified context passed to node if it is a mechanism (assigns context.source as ContextFlags.COMPOSITION) * - * • composition.py and progress.py: refactoring show_output and show_progress into: report_output, report_progress, report_simulations, report_to_devices * - * - * • report.py: updated docstrings with new args/attrs * • report.py: - refactored args to use "report" as root - added _use_rich_console and _use_rich_capture - TODO: allow independent controle of consolse and capture * • test_report.py: updated to use report_progress and report_to_devices * - * • report.py: - added independent rich divert and report recording * - * - * - * - * - * - * - * - * - * • report.py - report_output: fixes for simulation reporting * - * • composition.py - evaluate(): added use of Report context in call to composition.run() * - * - * - * - * - * - * • report.py: docstring added * - * - * - * - * • report.py: modify to use ReportOutput enum * - * - * • report.py: refactored to use ReportOutput enum * • report.py: refactored to use ReportDevices enum * • Project: refactored to use ReportOutput and ReportDevices enums • Component: reportOoutputPref property and setter: enforce assigment and retrieval of ReportOutput * • Project: refactored to use ReportProgress and ReportSimulations enums * - * - * • basepreferenceset.py: - modified reportOutputPref handling to convert False to ReportOutput.OFF and True to ReportOutput.TERSE * - * • report.py: allow reportOutputPrefs to be a list that includes 'params' and a ReportOutput option * - * - * - * • report.py, composition.py: (..._)progress_report(s) -> (...)run_reports(s) * - * • report.py: - added ReportOuput.ON as alias to ReportOutput.TERSE - fixed bug preventing progress reporting of simulations * - * • report.py: added temporary simulation_depth tracking * - * - * - * • report.py, composition.py, mechanism.py: merged and working with refactor/report/mech_report * - merged with devel * • report.py, composition.py: fixed indentations of progress report for nested comps and simulations * - * • report.py, composition.py: elaborated depth tracking to disinguish between nested comps and controller sims * • report.py - bug fix in simulation reporting * • report.py, composition.py - implement and use Report._nesting_stack and Report._control_stack * • report.py, composition.py - replaced _nesting and _control stacks with single _execution_stack and added _nested and _simulating attributes * • report.py: refactored to use _print_and_record_reports * - * - * • report.py: refactored _print_and_record_reports * - * - * - * - * • test_report.py: added tests for reports involving nested comps and simulations * - * - * • report.py: minor mods * • report.py: - support specification of individual params in reportOutputPref - support for indivdual function params still needs to be implemented * - merged with devel * • report.py: support specific params including functions latter still needs work * • report.py: - node_execution_report(): function params now included, and listed at end of params * • report.py: - node_execution_report(): function params now included, and listed at end of params * - * • report.py: - node_execution_report(): fixed bug in function parameter reporting (used primary function params for any secondary functions) * • report.py: - node_execution_report(): fixed bug in function parameter reporting (used primary function params for any secondary functions) * - * - * • preferenceset.py: augmented documentation for reportOutputPref and added examples * - * - * - * - * - * - * - * - * - * - * - * - * • report.py, composition.py, mechanism.py: added arguments and attributes for ReportParams * • report.py, composition.py, mechanism.py: added arguments and attributes for ReportParams * • report.py: - node_execution_report(): fix bugs * • composition.py: fix bugs for report_params * - * • report.py - node_execution_report(): implemented ReportParams.MODULATED * - * - * - * - * - * - * • report.py: supports ReportParams.MODULATED * - * - * - Co-authored-by: jdcpni --- .../core/components/mechanisms/mechanism.py | 8 +- psyneulink/core/compositions/composition.py | 76 +++-- psyneulink/core/compositions/report.py | 263 ++++++++++++++++-- .../compositions/autodiffcomposition.py | 11 +- 4 files changed, 302 insertions(+), 56 deletions(-) diff --git a/psyneulink/core/components/mechanisms/mechanism.py b/psyneulink/core/components/mechanisms/mechanism.py index 08752afca5b..e35a9eab1be 100644 --- a/psyneulink/core/components/mechanisms/mechanism.py +++ b/psyneulink/core/components/mechanisms/mechanism.py @@ -2308,6 +2308,7 @@ def execute(self, context=None, runtime_params=None, report_output=None, + report_params=None, run_report=None ): """Carry out a single `execution ` of the Mechanism. @@ -2535,17 +2536,20 @@ def execute(self, if (context.source == ContextFlags.COMMAND_LINE or context.execution_phase & (ContextFlags.PROCESSING | ContextFlags.LEARNING)): - from psyneulink.core.compositions.report import Report, ReportOutput - # Use report_output passed to execute from Composition or command line; + from psyneulink.core.compositions.report import Report, ReportOutput, ReportParams + # Use report_output and report_params options passed to execute from Composition or command line; # otherwise try to get from Mechanism's reportOutputPref report_output = report_output or next((pref for pref in convert_to_list(self.prefs.reportOutputPref) if isinstance(pref, ReportOutput)), None) + report_params = report_params or next((pref for pref in convert_to_list(self.prefs.reportOutputPref) + if isinstance(pref, ReportParams)), None) if report_output is not ReportOutput.OFF: with Report(self, context=context) as report: report.report_output(caller=self, report_num=run_report, scheduler=None, report_output=report_output, + report_params=report_params, content='node', context=context, node=self) diff --git a/psyneulink/core/compositions/composition.py b/psyneulink/core/compositions/composition.py index 98cf616ea17..d89cf6810c4 100644 --- a/psyneulink/core/compositions/composition.py +++ b/psyneulink/core/compositions/composition.py @@ -882,18 +882,18 @@ .. _Composition_Execution_Reporting: -*Reporting*. Executing a Composition returns the results of its last `TRIAL ` of execution. If -either `run ` or `learn ` is called, the results of all `TRIALS ` -executed are available in the Composition's `results ` attribute (see `Results -` for additional details). A report of the results of each -`TRIAL ` can also be generated as the Compostion is executing, using the **report_output** and -**report_progress** arguments of any of the execution methods. **report_output** generates a report of the input and -output the Composition and its `Nodes `, while **report_progress** shows a progress bar indicating -how many `TRIALS ` have been executed and an estimate of the time remaining to completion (see the -`execute `, `run ` and `learn ` methods for additional -details). These options are both False by default. The values of individual Components (and their `parameters -`) assigned during execution can also be recorded in their `log ` attribute using the -`Log` facility. +*Results, Reporting and Logging*. Executing a Composition returns the results of its last `TRIAL ` of +execution. If either `run ` or `learn ` is called, the results of all `TRIALS +` executed are available in the Composition's `results ` attribute (see `Results +` for additional details). A report of the results of each `TRIAL ` +can also be generated as the Compostion is executing, using the **report_output** and **report_progress** arguments +of any of the execution methods. **report_output** (specified using `ReportOutput` options) generates a report of the +input and output of the Composition and its `Nodes `, and optionally their `Parameters` (specified +in the **report_params** arg using `ReportParams` options); **report_progress** (specified using `ReportProgress` +options) shows a progress bar indicating how many `TRIALS ` have been executed and an estimate of +the time remaining to completion. These options are all OFF by default (see `Report` for additional details). +The values of individual Components (and their `parameters `) assigned during execution can also be +recorded in their `log ` attribute using the `Log` facility. *Inputs*. All methods of executing a Composition require specification of an **inputs** argument, which designates the values assigned to the `INPUT` `Nodes ` of the Composition for each `TRIAL `. @@ -2394,7 +2394,8 @@ def input_function(env, result): from psyneulink.core.components.projections.projection import ProjectionError, DuplicateProjectionError from psyneulink.core.components.shellclasses import Composition_Base from psyneulink.core.components.shellclasses import Mechanism, Projection -from psyneulink.core.compositions.report import Report, ReportOutput, ReportProgress, ReportSimulations, ReportDevices +from psyneulink.core.compositions.report import Report,\ + ReportOutput, ReportParams, ReportProgress, ReportSimulations, ReportDevices from psyneulink.core.compositions.showgraph import ShowGraph, INITIAL_FRAME, SHOW_CIM, EXECUTION_SET, SHOW_CONTROLLER from psyneulink.core.globals.context import Context, ContextFlags, handle_external_context from psyneulink.core.globals.keywords import \ @@ -7457,6 +7458,7 @@ def evaluate( execution_mode=execution_mode, skip_initialization=True, report_output=report._report_output, + report_params=report._report_params, report_progress=report._report_progress, report_simulations=report._report_simulations, report_to_devices=report._report_to_devices @@ -8023,6 +8025,7 @@ def run( termination_processing=None, skip_analyze_graph=False, report_output:ReportOutput=ReportOutput.OFF, + report_params:ReportParams=ReportParams.OFF, report_progress=ReportProgress.OFF, report_simulations=ReportSimulations.OFF, report_to_devices=None, @@ -8127,6 +8130,10 @@ def run( specifies whether to show output of the Composition and its `Nodes ` trial-by-trial as it is generated; see `Report_Output` for additional details and `ReportOutput` for options. + report_params : ReportParams : default ReportParams.OFF + specifies whether to show values the `Parameters` of the Composition and its `Nodes ` + as part of the output report; see `Report_Output` for additional details and `ReportParams` for options. + report_progress : ReportProgress : default ReportProgress.OFF specifies whether to report progress of execution in real time; see `Report_Progress` for additional details. @@ -8418,6 +8425,7 @@ def run( with Report(self, report_output=report_output, + report_params=report_params, report_progress=report_progress, report_simulations=report_simulations, report_to_devices=report_to_devices, @@ -8461,6 +8469,7 @@ def run( skip_initialization=True, execution_mode=execution_mode, report_output=report_output, + report_params=report_params, report_progress=report_progress, report_simulations=report_simulations, report=report, @@ -8632,17 +8641,21 @@ def learn( called after each minibatch is executed report_output : ReportOutput : default ReportOutput.OFF - specifies whether to show output of the Composition and its `Nodes ` trial-by-trial as - it is generated; see `Report_Output` for additional details and `ReportOutput` for options. + specifies whether to show output of the Composition and its `Nodes ` trial-by-trial + as it is generated; see `Report_Output` for additional details and `ReportOutput` for options. + + report_params : ReportParams : default ReportParams.OFF + specifies whether to show values the `Parameters` of the Composition and its `Nodes ` + as part of the output report; see `Report_Output` for additional details and `ReportParams` for options. report_progress : ReportProgress : default ReportProgress.OFF specifies whether to report progress of execution in real time; see `Report_Progress` for additional details. report_simulations : ReportSimulatons : default ReportSimulations.OFF - specifies whether to show output and/or progress for `simulations ` - executed by the Composition's `controller `; see `Report_Simulations` for - additional details. + specifies whether to show output and/or progress for `simulations + ` executed by the Composition's `controller + `; see `Report_Simulations` for additional details. report_to_devices : list(ReportDevices) : default ReportDevices.CONSOLE specifies where output and progress should be reported; see `Report_To_Devices` for additional @@ -8757,6 +8770,7 @@ def execute( skip_initialization=False, execution_mode:pnlvm.ExecutionMode = pnlvm.ExecutionMode.Python, report_output:ReportOutput=ReportOutput.OFF, + report_params:ReportOutput=ReportParams.OFF, report_progress:ReportProgress=ReportProgress.OFF, report_simulations:ReportSimulations=ReportSimulations.OFF, report_to_devices:ReportDevices=None, @@ -8826,6 +8840,10 @@ def execute( specifies whether to show output of the Composition and its `Nodes ` for the execution; see `Report_Output` for additional details and `ReportOutput` for options. + report_params : ReportParams : default ReportParams.OFF + specifies whether to show values the `Parameters` of the Composition and its `Nodes ` + for the execution; see `Report_Output` for additional details and `ReportParams` for options. + report_progress : ReportProgress : default ReportProgress.OFF specifies whether to report progress of the execution; see `Report_Progress` for additional details. @@ -8846,6 +8864,7 @@ def execute( with Report(self, report_output=report_output, + report_params=report_params, report_progress=report_progress, report_simulations=report_simulations, report_to_devices=report_to_devices, @@ -9147,7 +9166,14 @@ def execute( execution_sets.__next__() # Add TRIAL header and Composition's input to output report (now that they are known) - report.report_output(self, run_report, execution_scheduler, report_output, 'trial_init', context) + report.report_output(caller=self, + report_num=run_report, + scheduler=execution_scheduler, + report_output=report_output, + report_params=report_params, + content='trial_init', + context=context + ) for next_execution_set in execution_sets: @@ -9222,6 +9248,7 @@ def execute( report.report_output(self, run_report, execution_scheduler, report_output, + report_params, 'time_step_init', context, nodes_to_report=True) @@ -9293,6 +9320,7 @@ def execute( port._update(context=context) node.execute(context=mech_context, report_output=report_output, + report_params=report_params, run_report=run_report, runtime_params=execution_runtime_params, ) @@ -9360,6 +9388,7 @@ def execute( pnlvm.ExecutionMode.Python ret = node.execute(context=context, report_output=report_output, + report_params=report_params, report_progress=report_progress, execution_mode=nested_execution_mode) @@ -9380,6 +9409,7 @@ def execute( run_report, execution_scheduler, report_output, + report_params, 'node', context, node=node) @@ -9428,8 +9458,9 @@ def execute( # Complete TIME_STEP entry for output report - report.report_output(self, run_report, execution_scheduler, report_output, 'time_step', context, - nodes_to_report= nodes_to_report) + report.report_output(self, run_report, execution_scheduler, + report_output, report_params, 'time_step', context, + nodes_to_report= nodes_to_report) context.remove_flag(ContextFlags.PROCESSING) @@ -9497,7 +9528,8 @@ def execute( output_values.append(port.parameters.value._get(context)) # Complete TRIAL entry for output report, and report progress - report.report_output(self, run_report, execution_scheduler, report_output, 'trial', context) + report.report_output(self, run_report, execution_scheduler, report_output, + report_params, 'trial', context) report.report_progress(self, run_report, context) # UPDATE TIME and RETURN *********************************************************************************** diff --git a/psyneulink/core/compositions/report.py b/psyneulink/core/compositions/report.py index efea3138db5..9824c04fcbb 100644 --- a/psyneulink/core/compositions/report.py +++ b/psyneulink/core/compositions/report.py @@ -15,7 +15,7 @@ .. _Report_Output: -Output reporting +Output Reporting ---------------- Output reporting provides information about the input and output to a `Mechanism` or to a `Composition` and @@ -24,15 +24,18 @@ Mechanism's `execute ` method or any of a Composition's `execution methods `. If `USE_PREFS ` or `TERSE ` is used, reporting is generated as execution of each Component occurs; if `FULL ` is used, then the -information is reported at the end of each `TRIAL ` executed. Whether `simulations +information is reported at the end of each `TRIAL ` executed. This always includes the input and +output to a `Mechanism` or a `Composition` and its `Nodes `, and can also include the values +of their `Parameters`, depending on the specification of the **report_params** argument (using `ReportParams` options` +and/or the `reportOutputPref ` settings of individual Mechanisms. Whether `simulations ` executed by a Composition's `controller ` are included is determined by the **report_simulations** argument using a `ReportSimulations` option. Output is reported -to the devices specified in the **report_to_devices** argument using the `ReportDevices` options +to the devices specified in the **report_to_devices** argument using the `ReportDevices` options. .. _Report_Progress: -Progress reporting +Progress Reporting ------------------ Progress reporting provides information about the status of execution of a Composition's `run ` @@ -86,10 +89,8 @@ import warnings from enum import Enum, Flag, auto from io import StringIO -from functools import reduce import numpy as np - from rich import print, box from rich.console import Console, RenderGroup from rich.panel import Panel @@ -97,11 +98,11 @@ from psyneulink.core.globals.context import Context from psyneulink.core.globals.context import ContextFlags -from psyneulink.core.globals.keywords import FUNCTION_PARAMS, INPUT_PORTS, OUTPUT_PORTS +from psyneulink.core.globals.keywords import FUNCTION_PARAMS, INPUT_PORTS, OUTPUT_PORTS, VALUE from psyneulink.core.globals.utilities import convert_to_list -__all__ = ['Report', 'ReportOutput', 'ReportProgress', 'ReportDevices', 'ReportSimulations', - 'CONSOLE', 'RECORD', 'DIVERT', 'PNL_VIEW'] +__all__ = ['Report', 'ReportOutput', 'ReportParams', 'ReportProgress', 'ReportDevices', 'ReportSimulations', + 'CONSOLE', 'CONTROLLED', 'LOGGED', 'MODULATED', 'RECORD', 'DIVERT', 'PNL_VIEW', ] SIMULATION = 'Simulat' DEFAULT = 'Execut' @@ -166,6 +167,58 @@ class ReportOutput(Enum): FULL = 3 +class ReportParams(Enum): + """ + Options used in the **report_params** argument of a `Composition`\'s `execution methods + `, to specify the scope of reporting for its `Parameters` and those of its + `Nodes `; see `Reporting Parameter values ` under `Report_Output` for + additional details. + + .. technical_note:: + Use of these options is expected in the **report_output** constructor for the `Report` object, + and are used as the values of its `report_params ` attribute. + + Attributes + ---------- + + OFF + suppress reporting of parameter values. + + USE_PREFS + defers to `reportOutputPref ` settings of individual Components. + + MODULATED (aka CONTROLLED) + report all `Parameters` that are being `modulated ` (i.e., controlled) by a + `ControlMechanism` within the `Composition` (that is, those for which the corresponding `ParameterPort` + receives a `ControlProjection` from a `ControlSignal`. + + CONTROLLED (aka MODULATED) + report all `Parameters` that are being controlled (i.e., `modulated `) by a + `ControlMechanism` within the `Composition` (that is, those for which the corresponding `ParameterPort` + receives a `ControlProjection` from a `ControlSignal`. + + LOGGED + report all `Parameters` that are specified to be logged with `LogCondition.EXECUTION`; see `Log` for + additional details. + + ALL + enforce reporting of all `Parameters` of a `Composition` and its `Nodes `. + + """ + + OFF = 0 + MODULATED = auto() + CONTROLLED = MODULATED + MONITORED = auto() + LOGGED = auto() + ALL = auto() + +MODULATED = ReportParams.MODULATED +CONTROLLED = ReportParams.CONTROLLED +LOGGED = ReportParams.LOGGED +ALL = ReportParams.ALL + + class ReportProgress(Enum): """ Options used in the **report_progress** argument of a `Composition`\'s `run ` and `learn @@ -299,6 +352,9 @@ class Report: specifies whether to report output of the execution on a trial-by-trial as it is generated; see `ReportOutput` for options. + _report_params : list[ReportParams] : default [ReportParams.USE_PREFS] + specifies which params are reported if ReportOutput.FULL is in effect. + report_progress : ReportProgress : default ReportProgress.OFF specifies whether to report progress of each `TRIAL ` of a `Composition`\\'s execution, showing the number of `TRIALS ` that have been executed and a progress bar; see @@ -308,7 +364,7 @@ class Report: specifies whether to report output and progress for `simulations ` executed by an a Composition's `controller `; see `ReportSimulations` for options. - report_to_devices : list(ReportDevices) : default ReportDevices.CONSOLE + report_to_devices : list[ReportDevices] : default [ReportDevices.CONSOLE] specifies devices to which output and progress reporting is sent; see `ReportDevices` for options. Attributes @@ -324,18 +380,21 @@ class Report: identifies whether reporting is enabled; True if either the **_report_output** or **_report_progress** progress arguments of the constructor were specified as not False. - _report_output : bool, *TERSE*, or *FULL* : default False + _report_output : ReportOutput : default ReportOutput.OFF determines whether and, if so, what form of output is displayed and/or captured. - _report_progress : bool : default False + _report_params : list[ReportParams] : default [ReportParams.USE_PREFS] + determines which params are reported if ReportOutput.FULL is in effect. + + _report_progress : ReportProgress : default ReportProgress.OFF determines whether progress is displayed and/or captured. - _report_simulations : bool : default False + _report_simulations : ReportSimulations : default ReportSimulations.OFF determines whether reporting occurs for output and/or progress of `simulations ` carried out by the `controller ` of a `Composition`. - _report_to_devices : list + _report_to_devices : list[ReportDevices : [ReportDevices.CONSOLE] list of devices currently enabled for reporting. _use_rich : False, *CONSOLE*, *DIVERT* or list: default *CONSOLE* @@ -397,6 +456,7 @@ class Report: def __new__(cls, caller, report_output:ReportOutput=ReportOutput.OFF, + report_params:ReportParams=ReportParams.OFF, report_progress:ReportProgress=ReportProgress.OFF, report_simulations:ReportSimulations=ReportSimulations.OFF, report_to_devices:(list(ReportDevices.__members__), list)=ReportDevices.CONSOLE, @@ -427,6 +487,7 @@ def __new__(cls, # Assign option properties cls._report_progress = report_progress cls._report_output = report_output + cls._report_params = convert_to_list(report_params) cls._reporting_enabled = report_output is not ReportOutput.OFF or cls._report_progress cls._report_simulations = report_simulations cls._rich_console = ReportDevices.CONSOLE in cls._report_to_devices @@ -680,10 +741,12 @@ def report_progress(self, caller, report_num:int, context:Context): self._print_and_record_reports(PROGRESS_REPORT, context) - def report_output(self, caller, + def report_output(self, + caller, report_num:int, scheduler, report_output:ReportOutput, + report_params:list, content:str, context:Context, nodes_to_report:bool=False, @@ -711,6 +774,11 @@ def report_output(self, caller, `execution method ` or a Mechanism's `execute ` method. + report_params : [ReportParams] + conveys `ReportParams` option(s) specified in the **report_params** argument of the call to a Composition's + `execution method ` or a Mechanism's `execute ` + method. + content : str specifies content of current element of report; must be: 'trial_init', 'time_step_init', 'node', 'time_step', 'trial' or 'run'. @@ -818,6 +886,7 @@ def report_output(self, caller, input_val=node.get_input_values(context), output_val=node.output_port.parameters.value._get(context), report_output=node_report_type, + report_params=report_params, context=context ) # If trial is using FULL report, save Node's to run_report @@ -912,6 +981,7 @@ def node_execution_report(self, input_val=None, output_val=None, report_output=ReportOutput.USE_PREFS, + report_params=ReportParams.OFF, context=None): """ Generates formatted output report for the `node ` of a `Composition` or a `Mechanism`. @@ -987,6 +1057,7 @@ def node_execution_report(self, # Render params if specified ------------------------------------------------------------------------------- from psyneulink.core.components.shellclasses import Function + report_params = convert_to_list(report_params) params = {p.name: p._get(context) for p in node.parameters} try: # Check for PARAMS keyword (or 'parameters') and remove from node_prefs if there @@ -1000,19 +1071,142 @@ def node_execution_report(self, # If any are left, assume they are for the node's function function_params = node_params_prefs # Display parameters if any are specified - include_params = node_params or function_params or params_keyword + include_params = node_params or function_params or params_keyword or report_params except (TypeError, IndexError): - # FIX: SHOULD PUT ERROR MESSAGE HERE REGARDING BAD reportOutputPref SPEC? assert False, f'PROGRAM ERROR: Problem processing reportOutputPref args for {node.name}.' # include_params = False + params_string = '' + function_params_string = '' + if include_params: - # print("- params:") - params_string = (f"params:") - function_params_string = "" + + def param_is_specified(name, specified_set): + """Helper method: check whether param has been specified based on options""" + + from psyneulink.core.components.mechanisms.mechanism import Mechanism + from psyneulink.core.components.mechanisms.processing.objectivemechanism import ObjectiveMechanism + from psyneulink.core.components.mechanisms.processing.compositioninterfacemechanism \ + import CompositionInterfaceMechanism + from psyneulink.core.components.mechanisms.modulatory.modulatorymechanism \ + import ModulatoryMechanism_Base + + # Helper methods for testing whether param satisfies specifications ----------------------------- + + def get_controller(proj): + """Helper method: get modulator (controller) of modulated params""" + # if not isinstance(proj.sender.owner, CompositionInterfaceMechanism): + if isinstance(proj.sender.owner, ModulatoryMechanism_Base): + return proj.sender.owner.name + # mediating a projection from a ModulatoryMechanism in a Composition in which this is nested + if not isinstance(proj.sender.owner, CompositionInterfaceMechanism): + assert False, f'PROGRAM ERROR Projection to ParameterPort for {param_name} of {node.name} ' \ + f'from non ModulatoryMechanism' + # Recursively call to get ModulatoryMechanism in outer Composition + return get_controller(proj.sender.owner.afferents[0]) + + def is_modulated(): + """Helper method: determine whether parameter is being modulated + by checking whether ParameterPort receives aControlProjection + """ + try: + if isinstance(node, Mechanism): + if name in node.parameter_ports.names: + param_port = node.parameter_ports[name] + if param_port.mod_afferents: + controller_names = [get_controller(c) for c in param_port.mod_afferents] + controllers_str = ' and '.join(controller_names) + return f'modulated by {controllers_str}' + except: + print(f'Failed to find {name} on {node.name}') + # return '' + + + def get_monitor(proj): + """Helper method: get modulator (controller) of modulated params""" + # if not isinstance(proj.sender.owner, CompositionInterfaceMechanism): + if isinstance(proj.receiver.owner, (ObjectiveMechanism, ModulatoryMechanism_Base)): + return proj.receiver.owner.name + # Mediating a projection from a monitored Mechanism to a Composition in which it is nested, so + # recursively call to get receiver in outer Composition + if isinstance(proj.receiver.owner, CompositionInterfaceMechanism) and proj.receiver.owner.efferents: + # owners = [] + # for efferent in proj.receiver.owner.efferents: + # owner = get_monitor(efferent) + # if owner: + # owners.extend(owner) + # return(owners) + owners = [get_monitor(efferent) for efferent in proj.receiver.owner.efferents] + return owners + + + def is_monitored(): + """Helper method: determine whether parameter is being monitored by checking whether OutputPort + sends a MappingProjection to an ObjectiveMechanism or ControlMechanism. + """ + try: + if name is VALUE and isinstance(node, Mechanism): + monitor_names = [] + for output_port in node.output_ports: + monitors = [] + for proj in output_port.efferents: + monitor = get_monitor(proj) + if isinstance(monitor, list): + monitors.extend(monitor) + else: + monitors.append(monitor) + monitor_names.extend([monitor_name for monitor_name in monitors if monitor_name]) + if monitor_names: + monitor_str = ' and '.join(monitor_names) + return f'monitored by {monitor_str}' + except: + print(f'Failed to find {name} on {node.name}') + # return '' + + def is_logged(): + pass + + # Evaluate tests: ----------------------------------------------------------------------- + + # Get modulated and monitored descriptions if they apply + mod_str = is_modulated() + monitor_str = is_monitored() + if monitor_str and mod_str: + control_str = " and ".join([monitor_str, mod_str]) + else: + control_str = monitor_str or mod_str + if control_str: + control_str = f' ({control_str})' + + # Include if param is explicitly specified or ReportParams.ALL (or 'params') is specified + if (name in specified_set + # FIX: ADD SUPPORT FOR ReportParams.ALL + # PARAMS specified as keyword to display all params + or include_params is params_keyword): + return control_str or True + + # Include if param is modulated and ReportParams.MODULATED (CONTROLLED) is specified + if any(k in report_params for k in (ReportParams.MODULATED, ReportParams.CONTROLLED)) and mod_str: + return control_str + + # FIX: NEED TO FILTER OUT RESPONSES TO FUNCTION VALUE AND TO OBJECTIVE MECHANISM ISELF + # # Include if param is monitored and ReportParams.MONITORED is specified + # if ReportParams.MONITORED in report_params and monitor_str: + # return control_str + + # Include if param is being logged and ReportParams.LOGGED is specified + if report_params is ReportParams.LOGGED: + pass + + return False + + # Test whether param matches specifications: ----------------------------------------- + # Sort for consistency of output params_keys_sorted = sorted(params.keys()) for param_name in params_keys_sorted: + + # Check for function param_is_function = False # No need to report: # function_params here, as they will be reported for the function itself below; @@ -1029,15 +1223,21 @@ def node_execution_report(self, elif isinstance(param_value, (types.FunctionType, types.MethodType)): param = param_value.__node__.__class__.__name__ param_is_function = True - # Node_param - elif param_name in node_params or include_params is params_keyword: + + # Node param(s) + elif param_is_specified(param_name, node_params): # Put in params_string if param is specified or 'params' is specified param_value = params[param_name] + if not params_string: + # Add header + params_string = (f"params:") params_string += f"\n\t{param_name}: {str(param_value).__str__().strip('[]')}" if node_params: node_params.pop(node_params.index(param_name)) # Don't include functions in params_string yet (to keep at bottom of report) continue + + # Function param(s) if param_is_function: # Sort for consistency of output # func_params_keys_sorted = sorted(node.function.parameters.names()) @@ -1046,16 +1246,24 @@ def node_execution_report(self, for fct_param_name in func_params_keys_sorted: # Put in function_params_string if function param is specified or 'params' is specified # (appended to params_string below to keep functions at bottom of report) - if fct_param_name in function_params or include_params is params_keyword: + modulated = False + qualification = param_is_specified(fct_param_name, function_params) + if qualification: if not header_printed: function_params_string += f"\n\t{param_name}: {param_value.name.__str__().strip('[]')}" header_printed = True param_value = getattr(getattr(node,param_name).parameters,fct_param_name)._get(context) param_value = np.squeeze(param_value) param_value_str = str(param_value).__str__().strip('[]') - function_params_string += f"\n\t\t{fct_param_name}: {param_value_str}" + if not params_string: + params_string = (f"params:") + if isinstance(qualification, str): + qualification = qualification + else: + qualification = '' + function_params_string += f"\n\t\t{fct_param_name}: {param_value_str}{qualification}" if function_params: - function_params.pop(function_params.index(param_name)) + function_params.pop(function_params.index(fct_param_name)) assert not node_params, f"PROGRAM ERROR in execution of Report.node_execution_report() " \ f"for '{node.name}': {node_params} remaining in node_params." @@ -1065,13 +1273,12 @@ def node_execution_report(self, params_string += function_params_string - # Generate report ------------------------------------------------------------------------------- + # Generate report ------------------------------------------------------------------------------- - if include_params: + if params_string: width = 100 expand = True node_report = RenderGroup(input_report,Panel(params_string), output_report) - params_string else: width = None expand = False diff --git a/psyneulink/library/compositions/autodiffcomposition.py b/psyneulink/library/compositions/autodiffcomposition.py index bd06d157425..555ee97a083 100644 --- a/psyneulink/library/compositions/autodiffcomposition.py +++ b/psyneulink/library/compositions/autodiffcomposition.py @@ -144,6 +144,8 @@ from psyneulink.library.components.mechanisms.processing.objective.comparatormechanism import ComparatorMechanism from psyneulink.core.compositions.composition import Composition, NodeRole from psyneulink.core.compositions.composition import CompositionError +from psyneulink.core.compositions.report \ + import ReportOutput, ReportParams, ReportProgress, ReportSimulations, ReportDevices from psyneulink.core.globals.context import Context, ContextFlags, handle_external_context from psyneulink.core.globals.keywords import SOFT_CLAMP from psyneulink.core.scheduling.scheduler import Scheduler @@ -476,10 +478,11 @@ def execute(self, runtime_params=None, execution_mode:pnlvm.ExecutionMode = pnlvm.ExecutionMode.Python, skip_initialization=False, - report_output=False, - report_progress=False, - report_simulations=False, - report_to_devices=None, + report_output:ReportOutput=ReportOutput.OFF, + report_params:ReportOutput=ReportParams.OFF, + report_progress:ReportProgress=ReportProgress.OFF, + report_simulations:ReportSimulations=ReportSimulations.OFF, + report_to_devices:ReportDevices=None, report=None, run_report=None, ):