diff --git a/.github/workflows/CI.yaml b/.github/workflows/CI.yaml index 32d7e44..b168427 100644 --- a/.github/workflows/CI.yaml +++ b/.github/workflows/CI.yaml @@ -61,11 +61,13 @@ jobs: pytest -v --cov=lpath --cov-report=xml --color=yes lpath/tests/ - name: CodeCov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: - # token: ${{ secrets.CODECOV_TOKEN }} # Not needed for Public Repos file: ./coverage.xml flags: unittests name: codecov-${{ matrix.os }}-py${{ matrix.python-version }} fail_ci_if_error: false verbose: false + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} # Not needed for Public Repos + diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index a576c0b..4bf04c9 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -101,7 +101,7 @@ jobs: with: # unpacks default artifact into dist/ # if `name: artifact` is omitted, the action will create extra parent dir - name: artifact-* + pattern: artifact-* path: dist merge-multiple: true diff --git a/devtools/conda-envs/environment-rtd.yml b/devtools/conda-envs/environment-rtd.yml index bbcf848..20d03b0 100644 --- a/devtools/conda-envs/environment-rtd.yml +++ b/devtools/conda-envs/environment-rtd.yml @@ -3,7 +3,7 @@ channels: - conda-forge - defaults dependencies: - - python + - python<3.12 - westpa>=2022.03 - scikit-learn - tqdm diff --git a/devtools/conda-envs/test_env.yaml b/devtools/conda-envs/test_env.yaml index 4f632d3..50bc848 100644 --- a/devtools/conda-envs/test_env.yaml +++ b/devtools/conda-envs/test_env.yaml @@ -4,7 +4,7 @@ channels: - defaults dependencies: # Base depends - - python + - python<3.12 - pip - scikit-learn - tqdm diff --git a/docs/usage.rst b/docs/usage.rst index e0c8299..62fa49b 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -92,7 +92,8 @@ In this step, we will pattern match any successful transitions we've identified 1. From the command line, run the following:: - lpath match --input-pickle succ_traj/pathways.pickle --cluster-labels-output succ_traj/cluster_labels.npy + lpath match --input-pickle succ_traj/pathways.pickle --output-pickle succ_traj/match-output.pickle \ + --cluster-labels-output succ_traj/cluster_labels.npy 2. After the comparison process is completed, it should show you the dendrogram. Closing the figure should trigger prompts to guide you further. @@ -100,9 +101,22 @@ In this step, we will pattern match any successful transitions we've identified Plot ____ +This step will help you plot some of the most common graphs, such as dendrograms and histograms, directly from the pickle object generated from match. Users may also elect to use the plotting scripts from the ``examples`` folder. +There is a script to plot ``NetworkX`` plots there. -[UNDER CONSTRUCTION] +More specifically, the following graphs will be made in the ``plots`` folder:: +* Dendrogram showing separation between clusters +* Weights/Cluster bar graph +* Target iteration histograms (per cluster) +* Event duration histograms (per cluster) + + +From the command line, run the following and it should generate a separate file for each of the above graphs:: + + lpath plot --plot-input succ_traj/match-output.pickle + +More options for customizing the graphs can be found by running ``lpath plot --help``. Weighted Ensemble Simulations ----------------------------- @@ -144,7 +158,7 @@ This will do the pattern matching and output individual h5 files for each cluste 1. From the command line, run the following:: - lpath match -we --input-pickle succ_traj/output.pickle --cluster-labels-output succ_traj/cluster_labels.npy \ + lpath match -we --input-pickle succ_traj/output.pickle --output-pickle succ_traj/match-output.pickle --cluster-labels-output succ_traj/cluster_labels.npy \ --export-h5 --file-pattern "west_succ_c{}.h5" 2. After the comparison process is completed, it should show you the dendrogram. Closing the figure should trigger prompts to guide you further. @@ -154,11 +168,90 @@ This will do the pattern matching and output individual h5 files for each cluste For cases where you want to run pattern matching comparison between segment IDs, you will have to use the largest common substring ``--substring`` option. By default, the longest common subsequence algorithm is used.:: - lpath match -we --input-pickle succ_traj/output.pickle --cluster-labels-output succ_traj/cluster_labels.npy \ - --export-h5 --file-pattern "west_succ_c{}.h5" --reassign-function "reassign_segid" --substring + lpath match -we --input-pickle succ_traj/output.pickle --output-pickle succ_traj/match-output.pickle --cluster-labels-output succ_traj/cluster_labels.npy \ + --export-h5 --file-pattern "west_succ_c{}.h5" --reassign-method "reassign_segid" --substring Plot ____ +This step will help you plot some of the most common graphs, such as dendrograms and histograms, directly from the pickle object generated from match. Users may also elect to use the plotting scripts from the ``examples`` folder. +There is a script to plot ``NetworkX`` plots there. + +More specifically, the following graphs will be made in the ``plots`` folder:: + +* Dendrogram showing separation between clusters +* Weights/Cluster bar graph +* Target iteration histograms (per cluster) +* Event duration histograms (per cluster) + + +From the command line, run the following and it should generate a separate file for each of the above graphs:: + + lpath plot --plot-input succ_traj/match-output.pickle + +More options for customizing the graphs can be found by running ``lpath plot --help``. + + +Example Reassign file +--------------------- + +The following is a reassign function if you decides to reclassify your states:: -[UNDER CONSTRUCTION] \ No newline at end of file + def reassign_custom(data, pathways, dictionary, assign_file=None): + """ + Reclassify/assign frames into different states. This is highly + specific to the system. If w_assign's definition is sufficient, + you can proceed with what's made in the previous step + using ``reassign_identity``. + + In this example, the dictionary maps state idx to its corresponding ``state_string``. + We suggest using alphabets as states. + + Parameters + ---------- + data : list + An array with the data necessary to reassign, as extracted from ``output.pickle``. + + pathways : numpy.ndarray + An empty array with shapes for iter_id/seg_id/state_id/pcoord_or_auxdata/frame#/weight. + + dictionary : dict + An empty dictionary obj for mapping ``state_id`` with ``state string``. The last entry in + the dictionary should be the "unknown" state. + + assign_file : str, default : None + A string pointing to the ``assign.h5`` file. Needed as a parameter for all functions, + but is ignored if it's an MD trajectory. + + Returns + ------- + dictionary : dict + A dictionary mapping each ``state_id`` (float/int) with a ``state string`` (character). + The last entry in the dictionary should be the "unknown" state. + + """ + # Other example for grouping multiple states into one. + for idx, pathway in enumerate(data): + # The following shows how you can "merge" multiple states into + # a single one. + pathway = numpy.asarray(pathway) + # Further downsizing... to if pcoord is less than 5 + first_contact = numpy.where(pathway[:, 3] < 5)[0][0] + for jdx, frame in enumerate(pathway): + # First copy all columns over + pathways[idx, jdx] = frame + # ortho is assigned to state 0 + if frame[2] in [1, 3, 4, 6, 7, 9]: + frame[2] = 0 + # para is assigned to state 1 + elif frame[2] in [2, 5, 8]: + frame[2] = 1 + # Unknown state is assigned 2 + if jdx < first_contact: + frame[2] = 2 + pathways[idx, jdx] = frame + + # Generating a dictionary mapping each state + dictionary = {0: 'A', 1: 'B', 2: '!'} + + return dictionary diff --git a/environment.yml b/environment.yml index 33722e4..99dfccc 100644 --- a/environment.yml +++ b/environment.yml @@ -3,10 +3,10 @@ channels: - conda-forge - defaults dependencies: - - python<3.11 + - python<3.12 - westpa>=2022.03 - scikit-learn - - matplotlib + - matplotlib>=3.6.0 - tqdm - networkx - pip diff --git a/examples/WE/reassign_custom.py b/examples/WE/reassign_custom.py index abb54a7..5a5588a 100644 --- a/examples/WE/reassign_custom.py +++ b/examples/WE/reassign_custom.py @@ -1,11 +1,43 @@ import numpy def reassign_custom(data, pathways, dictionary, assign_file=None): + """ + Reclassify/assign frames into different states. This is highly + specific to the system. If w_assign's definition is sufficient, + you can proceed with what's made in the previous step + using ``reassign_identity``. - for idx, val in enumerate(data): + In this example, the dictionary maps state idx to its corresponding ``state_string``. + We suggest using alphabets as states. + + Parameters + ---------- + data : list + An array with the data necessary to reassign, as extracted from ``output.pickle``. + + pathways : numpy.ndarray + An empty array with shapes for iter_id/seg_id/state_id/pcoord_or_auxdata/frame#/weight. + + dictionary : dict + An empty dictionary obj for mapping ``state_id`` with ``state string``. The last entry in + the dictionary should be the "unknown" state. + + assign_file : str, default : None + A string pointing to the ``assign.h5`` file. Needed as a parameter for all functions, + but is ignored if it's an MD trajectory. + + Returns + ------- + dictionary : dict + A dictionary mapping each ``state_id`` (float/int) with a ``state string`` (character). + The last entry in the dictionary should be the "unknown" state. + + """ + # reassign states to be the cluster IDs + for idx, val in enumerate(data): # Loop through each set of successful pathways val_arr = numpy.asarray(val) - for idx2, val2 in enumerate(val_arr): - val2[2] = int(val2[5]) + for idx2, val2 in enumerate(val_arr): # Loop through each frame of the pathway + val2[2] = int(val2[-3]) # Renumber state_id the with the aux dataset pathways[idx, idx2] = val2 # Generating a dictionary mapping each state diff --git a/examples/cMD/reassign_custom.py b/examples/cMD/reassign_custom.py index 98db7bc..5a5588a 100644 --- a/examples/cMD/reassign_custom.py +++ b/examples/cMD/reassign_custom.py @@ -1,12 +1,43 @@ import numpy def reassign_custom(data, pathways, dictionary, assign_file=None): + """ + Reclassify/assign frames into different states. This is highly + specific to the system. If w_assign's definition is sufficient, + you can proceed with what's made in the previous step + using ``reassign_identity``. + In this example, the dictionary maps state idx to its corresponding ``state_string``. + We suggest using alphabets as states. + + Parameters + ---------- + data : list + An array with the data necessary to reassign, as extracted from ``output.pickle``. + + pathways : numpy.ndarray + An empty array with shapes for iter_id/seg_id/state_id/pcoord_or_auxdata/frame#/weight. + + dictionary : dict + An empty dictionary obj for mapping ``state_id`` with ``state string``. The last entry in + the dictionary should be the "unknown" state. + + assign_file : str, default : None + A string pointing to the ``assign.h5`` file. Needed as a parameter for all functions, + but is ignored if it's an MD trajectory. + + Returns + ------- + dictionary : dict + A dictionary mapping each ``state_id`` (float/int) with a ``state string`` (character). + The last entry in the dictionary should be the "unknown" state. + + """ # reassign states to be the cluster IDs - for idx, val in enumerate(data): + for idx, val in enumerate(data): # Loop through each set of successful pathways val_arr = numpy.asarray(val) - for idx2, val2 in enumerate(val_arr): - val2[2] = int(val2[3]) + for idx2, val2 in enumerate(val_arr): # Loop through each frame of the pathway + val2[2] = int(val2[-3]) # Renumber state_id the with the aux dataset pathways[idx, idx2] = val2 # Generating a dictionary mapping each state diff --git a/lpath/argparser.py b/lpath/argparser.py index d1491d7..1dee8c6 100644 --- a/lpath/argparser.py +++ b/lpath/argparser.py @@ -2,9 +2,10 @@ All argument parsing from commandline is dealt here. """ import argparse -from argparse import ArgumentTypeError -from lpath._logger import Logger +from argparse import ArgumentTypeError, Namespace from ast import literal_eval + +from lpath._logger import Logger from lpath.io import default_dendrogram_colors log = Logger().get_logger(__name__) @@ -344,8 +345,9 @@ def add_extract_args(parser=None): help='Use Ray work manager. On by default.') raygroup.add_argument('--no-ray', '-NR', dest='use_ray', action='store_false', help='Do not use Ray. This overrides ``--use-ray``.') - raygroup.add_argument('--threads', '-t', type=check_non_neg, default=0, help='Number of threads to use ' - 'with Ray. The default of ``0`` uses all available resources detected.') + raygroup.add_argument('--threads', '-t', type=check_non_neg, default=0, + help='Number of threads to use with Ray. The default of ``0`` uses ' + 'all available resources detected.') extract_we = parser.add_argument_group('WE-specific Extract Parameters') @@ -406,18 +408,18 @@ def add_match_args(parser=None): match_io.add_argument('--input-pickle', '-ip', '--IP', '--pickle', dest='extract_output', default='succ_traj/output.pickle', type=str, help='Path to pickle object from the `extract` ' - 'step.') + 'step.') match_io.add_argument('--output-pickle', '-op', '--OP', dest='output_pickle', default='succ_traj/pathways.pickle', type=str, help='Path to reassigned object to be ' - 'outputted from the `match` step.') - match_io.add_argument('--cl-output', '-co', '--cluster-label-output', dest='cl_output', + 'outputted from the `match` step.') + match_io.add_argument('--cl-output', '-co', '--cluster-label-output', '--cluster-labels-output', dest='cl_output', default='succ_traj/cluster_labels.npy', type=str, help='Output file location for cluster labels.') match_io.add_argument('--match-exclude-min-length', '-me', '--match-exclude-length', '--match-exclude-short', dest='exclude_short', type=check_non_neg, default=0, help='Exclude trajectories shorter than provided value during ' 'matching. Default is 0, which will include trajectories of all lengths.') - match_io.add_argument('--reassign', '-ra', '--reassign-method', dest='reassign_method', + match_io.add_argument('--reassign', '-ra', '--reassign-method', '--reassign-function', dest='reassign_method', default='reassign_identity', type=str, help='Reassign method to use. Could be one of the defaults or a module to load. Defaults are ' '``reassign_identity``, ``reassign_statelabel``, ``reassign_segid``, ' @@ -461,7 +463,7 @@ def add_match_args(parser=None): help='Do not remake distance matrix.') match_io.add_argument('--remake-file', '--remade-file', '-dF', dest='dmatrix_save', type=str, default='succ_traj/distmat.npy', help='Path to pre-calculated distance matrix. Make sure ' - 'the ``--no-remake`` flag is specified.') + 'the ``--no-remake`` flag is specified.') match_io.add_argument('--remake-parallel', '-dP', dest='dmatrix_parallel', type=int, help='Number of jobs to run with the pairwise distance calculations. The default=None issues ' 'one job. A value of -1 uses all available resources. This is directly passed to the ' @@ -538,6 +540,8 @@ def add_plot_args(parser=None): plot_io.add_argument('--n-clusters', '-nc', '--num-clusters', dest='num_clusters', type=check_positive, help='For cases where you know in advance how many clusters you want for ' 'the hierarchical clustering.') + plot_io.add_argument('--timeout', '-pto', '--plot-timeout', dest='plot_timeout', type=check_non_neg, + default=None, help='Timeout (in seconds) for asking input.') # plot_io.add_argument('--plot-regen-cl', '-rcl', '--plot-regenerate-cluster-labels', dest='regen_cl', # action='store_true', @@ -764,3 +768,19 @@ def check_argv(): if 1 < len(sys.argv) < 3 and sys.argv[1] in all_options: log.warning(f'Running {sys.argv[1]} with all default values. Make sure you\'re sure of this!') + + +class DefaultArgs: + """ + Convenience class that could be used to call all the default arguments for each subparser. + """ + def __init__(self): + self.parser = create_parser() + self.subparsers = [] + self.parser, self.subparsers = create_subparsers(self.parser, self.subparsers) + + self.discretize = self.subparsers[0].parse_args('') + self.extract = self.subparsers[1].parse_args('') + self.match = self.subparsers[2].parse_args('') + self.plot = self.subparsers[3].parse_args('') + self.all = self.subparsers[4].parse_args('') diff --git a/lpath/extract.py b/lpath/extract.py index b30c667..611ecd6 100644 --- a/lpath/extract.py +++ b/lpath/extract.py @@ -571,7 +571,8 @@ def trace_seg_to_last_state( for frame_index in frame_loop: indv_trace.append([iteration_num, segment_num, corr_assign[frame_index], *ad_arr[frame_index], frame_index, weight]) - break + if trace_basis is False: + break else: # Just a normal iteration where we reached target state. Output everything in stride. frame_loop = frame_range(-1, term_frame_num, total_frames, stride_step) @@ -713,7 +714,8 @@ def trace_seg_to_last_state( for frame_index in frame_loop: indv_trace.append([iteration_num, segment_num, corr_assign[frame_index], *ad_arr[frame_index], frame_index, weight]) - break + if trace_basis is False: + break else: # Just a normal iteration where we reached target state. Output everything in stride. frame_loop = frame_range(-1, term_frame_num, total_frames, stride_step) diff --git a/lpath/lpath.py b/lpath/lpath.py index 4cc9343..68d28dd 100644 --- a/lpath/lpath.py +++ b/lpath/lpath.py @@ -48,14 +48,12 @@ def entry_point(): argparser.check_argv() - parser.print_usage() - # print(parser.__dict__) args = argparser.process_args(parser) log.info(f'LPATH arguments: {args}') make_dir(args) - print(args) + # print(args) # Run whatever function given args.func(args) diff --git a/lpath/match.py b/lpath/match.py index dd7ff1a..ab6a982 100644 --- a/lpath/match.py +++ b/lpath/match.py @@ -395,7 +395,8 @@ def reassign_custom(data, pathways, dictionary, assign_file=None): An empty array with shapes for iter_id/seg_id/state_id/pcoord_or_auxdata/frame#/weight. dictionary : dict - An empty dictionary obj for mapping ``state_id`` with ``state string``. + An empty dictionary obj for mapping ``state_id`` with ``state string``. The last entry in + the dictionary should be the "unknown" state. assign_file : str, default : None A string pointing to the ``assign.h5`` file. Needed as a parameter for all functions, @@ -405,6 +406,7 @@ def reassign_custom(data, pathways, dictionary, assign_file=None): ------- dictionary : dict A dictionary mapping each ``state_id`` (float/int) with a ``state string`` (character). + The last entry in the dictionary should be the "unknown" state. """ # Other example for grouping multiple states into one. @@ -436,11 +438,11 @@ def reassign_custom(data, pathways, dictionary, assign_file=None): def reassign_statelabel(data, pathways, dictionary, assign_file): """ - Use ``assign.h5`` states as is with ``statelabels``. Does not reclassify/assign frames + Use ``assign.h5`` states as is with ``state_labels``. Does not reclassify/assign frames into new states. - In this example, the dictionary maps state idx to its ``statelabels``, - as defined in the assign.h5. We suggest using alphabets as ``statelabels`` + In this example, the dictionary maps state idx to its ``state_labels``, + as defined in the assign.h5. We suggest using alphabets as ``state_labels`` to allow for more than 9 states. Parameters @@ -522,7 +524,7 @@ def reassign_segid(data, pathways, dictionary, assign_file=None): def reassign_identity(data, pathways, dictionary, assign_file=None): """ Use assign.h5 states as is. Does not attempt to map assignment - to ``statelabels`` from assign.h5. + to ``state_labels`` from assign.h5. Parameters ---------- @@ -938,7 +940,7 @@ def export_we_files(data_arr, weights, cluster_labels, clusters, file_pattern="w f.writelines(representative_list) -def determine_rerun(z, out_path='plots', mpl_colors=default_dendrogram_colors, ax=None): +def determine_rerun(z, out_path='plots', mpl_colors=default_dendrogram_colors, ax=None, timeout=None): """ Asks if you want to regenerate the dendrogram. @@ -955,11 +957,18 @@ def determine_rerun(z, out_path='plots', mpl_colors=default_dendrogram_colors, a ax : matplotlib.Axes, Default: None Matplotlib.Axes object to be inherited. + + timeout : int, default: 30 + Input timeout in seconds. + """ + if timeout is None: + timeout = 30 + while True: try: ans = timedinput('Do you want to regenerate the graph with a new threshold (y/[n])?\n', - timeout=60, default='N') + timeout=timeout, default='N') if ans == 'y' or ans == 'Y': ans2 = timedinput('What new threshold would you like?\n', timeout=15, default=0.5) try: @@ -967,7 +976,7 @@ def determine_rerun(z, out_path='plots', mpl_colors=default_dendrogram_colors, a show_fig=True, mpl_colors=mpl_colors, ax=ax) return ax except ValueError: - determine_rerun(z, out_path=out_path, mpl_colors=mpl_colors, ax=ax) + determine_rerun(z, out_path=out_path, mpl_colors=mpl_colors, ax=ax, timeout=timeout) elif ans == 'n' or ans == 'N' or ans == '': return None else: @@ -976,16 +985,19 @@ def determine_rerun(z, out_path='plots', mpl_colors=default_dendrogram_colors, a sys.exit(0) -def ask_number_clusters(num_clusters=None): +def ask_number_clusters(num_clusters=None, timeout=None): """ Asks how many clusters you want to separate the trajectories into. """ + if timeout is None: + timeout = 15 + if not num_clusters: while True: try: ans = timedinput('How many clusters would you like to separate the pathways into?\n', - timeout=15, default=2) + timeout=timeout, default=2) try: ans = int(ans) return ans @@ -993,6 +1005,8 @@ def ask_number_clusters(num_clusters=None): log.warning("Invalid input.\n") except KeyboardInterrupt: sys.exit(0) + else: + return num_clusters def report_statistics(n_clusters, cluster_labels, weights, segid_status=False): @@ -1092,9 +1106,10 @@ def main(arguments): z = calc_linkage(dist_matrix) ax = visualize(z, threshold=arguments.dendrogram_threshold, out_path=arguments.out_path, show_fig=arguments.dendrogram_show, mpl_colors=arguments.mpl_colors) - ax = determine_rerun(z, out_path=arguments.out_path, mpl_colors=arguments.mpl_colors, ax=ax) + ax = determine_rerun(z, out_path=arguments.out_path, mpl_colors=arguments.mpl_colors, ax=ax, + timeout=arguments.plot_timeout) - n_clusters = ask_number_clusters(arguments.num_clusters) + n_clusters = ask_number_clusters(arguments.num_clusters, timeout=arguments.plot_timeout) cluster_labels = hcluster(z, n_clusters) # Report statistics diff --git a/lpath/plot.py b/lpath/plot.py index 7d2cd65..c775f21 100644 --- a/lpath/plot.py +++ b/lpath/plot.py @@ -151,7 +151,7 @@ def __init__(self, arguments): for pathway in self.pathways: non_zero = pathway[numpy.nonzero(pathway[:, 0])] # Removing padding frames... weights.append(non_zero[-1, -1]) - durations.append(len(non_zero)) + durations.append(len(non_zero) / arguments.stride) target_iter.append(non_zero[-1][0]) iter_num += [frame[0] for frame in non_zero] states.append(pathway[:, 2]) @@ -169,6 +169,7 @@ def __init__(self, arguments): self.num_iter = numpy.asarray(iter_num, dtype=object) self.target_iter = numpy.asarray(target_iter) self.num_clusters = len(path_indices) + self.stride = arguments.stride # weighted_counts = [numpy.sum(self.weights[self.states == i]) for i in range(self.num_clusters)] # self.weighted_counts = numpy.array(weighted_counts, dtype=float) @@ -192,6 +193,9 @@ def __init__(self, arguments): self.show_fig = arguments.dendrogram_show self.ax_idx = 0 + if not self.show_fig: + matplotlib.use('agg') + def plt_config(self, ax_idx=None, separate=None): """ Process matplotlib arguments and append fig/axis objects to class. diff --git a/lpath/tests/test_argparser.py b/lpath/tests/test_argparser.py index 6421a8b..d53890b 100644 --- a/lpath/tests/test_argparser.py +++ b/lpath/tests/test_argparser.py @@ -124,7 +124,7 @@ def test_dimensions(self, create_ref_parser): assert isinstance(output, argparse.ArgumentParser) # Need to change as number of options increase - assert len(output._actions) == 66 + assert len(output._actions) == 67 def test_common_arguments(self, create_ref_parser): """ @@ -180,7 +180,7 @@ def test_plot_arguments(self, create_ref_parser): """ output, test_output = create_ref_parser test_input = ['-ipl', '-icl', '-pdF', '-pod', '-sty', '-mpl', '-col', '-pdt', '-pds', - '-pdh', '-nc', '-prl'] + '-pdh', '-nc', '-prl', '-pto'] for option in test_input: assert option in test_output diff --git a/pyproject.toml b/pyproject.toml index acc2217..6dad6da 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,7 +33,7 @@ dependencies = [ "ray", "tqdm", "networkx", - "matplotlib", + "matplotlib>=3.6.0", "timedinput", "importlib-resources;python_version<'3.10'" ] @@ -55,7 +55,7 @@ westpa = [ "westpa>=2022.03" ] tui = [ - "argparse-tui" + "argparse-tui>0.2.4" ] dev = [ "lpath-md[test,westpa,tui]" diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..8bc57df --- /dev/null +++ b/pytest.ini @@ -0,0 +1,4 @@ +[pytest] +filterwarnings = + ignore:.*invalid escape sequence.*:SyntaxWarning + ignore:.*invalid escape sequence.*:DeprecationWarning