From 809988583074dd005ca53f7767e1631182b5810a Mon Sep 17 00:00:00 2001 From: Gromwud Date: Thu, 7 May 2026 15:13:03 +0300 Subject: [PATCH 1/8] tests --- epde/integrate/deepxde_integration.py | 1 + projects/pic/data/aizawa/aizawa.npz | Bin 0 -> 9126 bytes projects/pic/data/aizawa/aizawa.py | 136 ++++++++++++++ projects/pic/data/apoptosis/apoptosis.npz | Bin 0 -> 9126 bytes projects/pic/data/apoptosis/apoptosis.py | 136 ++++++++++++++ .../pic/data/autocatalysis/autocatalysis.npz | Bin 0 -> 4326 bytes .../pic/data/autocatalysis/autocatalysis.py | 170 ++++++++++++++++++ .../autocatalytic-gene-switching.npz | Bin 0 -> 4326 bytes .../bacterial-respiration.npz | Bin 0 -> 6726 bytes .../bacterial-respiration.py | 135 ++++++++++++++ projects/pic/data/ns/ns.py | 4 +- 11 files changed, 580 insertions(+), 2 deletions(-) create mode 100644 projects/pic/data/aizawa/aizawa.npz create mode 100644 projects/pic/data/aizawa/aizawa.py create mode 100644 projects/pic/data/apoptosis/apoptosis.npz create mode 100644 projects/pic/data/apoptosis/apoptosis.py create mode 100644 projects/pic/data/autocatalysis/autocatalysis.npz create mode 100644 projects/pic/data/autocatalysis/autocatalysis.py create mode 100644 projects/pic/data/autocatalysis/autocatalytic-gene-switching.npz create mode 100644 projects/pic/data/bacterial-respiration/bacterial-respiration.npz create mode 100644 projects/pic/data/bacterial-respiration/bacterial-respiration.py diff --git a/epde/integrate/deepxde_integration.py b/epde/integrate/deepxde_integration.py index f1aa347b..4e34135e 100644 --- a/epde/integrate/deepxde_integration.py +++ b/epde/integrate/deepxde_integration.py @@ -259,6 +259,7 @@ def __init__(self, pretrained_net=None, **config): self.num_boundary = int(self.config.get('num_boundary', 500)) self.num_initial = int(self.config.get('num_initial', 500)) self.epochs = int(self.config.get('epochs', 10000)) + # self.iterations = int(self.config.get('epochs', 5)) self.bc_type = self.config.get('bc_type', 'Dirichlet') self.fallback_bc_value = self.config.get('fallback_bc_value', 0.0) diff --git a/projects/pic/data/aizawa/aizawa.npz b/projects/pic/data/aizawa/aizawa.npz new file mode 100644 index 0000000000000000000000000000000000000000..e1c460a438efbf938e63a2f5b369168f156b7851 GIT binary patch literal 9126 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 bool: + metaparams = {('sparsity', var): {'optimizable': False, 'value': 1E-6} for var in all_vars} + + correct_eq = translate_equation(correct_symbolic, search_obj.pool, all_vars=all_vars) + for var in all_vars: + correct_eq.vals[var].main_var_to_explain = var + correct_eq.vals[var].metaparameters = metaparams + print(correct_eq.text_form) + + incorrect_eq = translate_equation(eq_incorrect_symbolic, search_obj.pool, + all_vars=all_vars) # , all_vars = ['u', 'v']) + for var in all_vars: + incorrect_eq.vals[var].main_var_to_explain = var + incorrect_eq.vals[var].metaparameters = metaparams + print(incorrect_eq.text_form) + + fit_operator.apply(correct_eq, {}) + fit_operator.apply(incorrect_eq, {}) + print([[correct_eq.vals[var].fitness_value, incorrect_eq.vals[var].fitness_value] for var in all_vars]) + print([[correct_eq.vals[var].coefficients_stability, incorrect_eq.vals[var].coefficients_stability] for var in + all_vars]) + print([[correct_eq.vals[var].aic, incorrect_eq.vals[var].aic] for var in all_vars]) + + # print([correct_eq.vals[var].coefficients_stability < incorrect_eq.vals[var].coefficients_stability for var in all_vars]) + return all([correct_eq.vals[var].coefficients_stability < incorrect_eq.vals[var].coefficients_stability for var in + all_vars]) + + +def prepare_suboperators(fitness_operator: CompoundOperator, operator_params: dict) -> CompoundOperator: + sparsity = LASSOSparsity() + coeff_calc = LinRegBasedCoeffsEquation() + + # sparsity = map_operator_between_levels(sparsity, 'gene level', 'chromosome level') + # coeff_calc = map_operator_between_levels(coeff_calc, 'gene level', 'chromosome level') + + fitness_operator.set_suboperators({'sparsity': sparsity, + 'coeff_calc': coeff_calc}) + fitness_cond = lambda x: not getattr(x, 'fitness_calculated') + fitness_operator.params = operator_params + fitness_operator = map_operator_between_levels(fitness_operator, 'gene level', 'chromosome level', + objective_condition=fitness_cond) + return fitness_operator + + +def aizawa_discovery(noise_level): + data_file = os.path.join(os.path.dirname(__file__), 'aizawa.npz') + data = np.load(data_file) + t = data['t'] + u = data['u'] + + x = u[..., 0] + y = u[..., 1] + z = u[..., 2] + dimensionality = x.ndim - 1 + + trig_tokens = TrigonometricTokens(freq=(2 - 1e-8, 2 + 1e-8), + dimensionality=dimensionality) + grid_tokens = GridTokens(['x_0', ], dimensionality=dimensionality, max_power=2) + + epde_search_obj = EpdeSearch(use_solver=False, multiobjective_mode=True, use_pic=True, boundary=15, + coordinate_tensors=(t,), verbose_params={'show_iter_idx': True}, + device='cuda') + + epde_search_obj.set_preprocessor(default_preprocessor_type='FD', + preprocessor_kwargs={}) + + popsize = 16 + epde_search_obj.set_moeadd_params(population_size=popsize, training_epochs=50) + + factors_max_number = {'factors_num': [1, 2], 'probas' : [0.8, 0.2]} + + epde_search_obj.fit(data=[x, y, z], variable_names=['x', 'y', 'z'], max_deriv_order=(1,), + equation_terms_max_number=7, data_fun_pow=3, additional_tokens=[], + equation_factors_max_number=factors_max_number, + eq_sparsity_interval=(1e-8, 1e-0)) # + + epde_search_obj.equations(only_print=True, num=1) + epde_search_obj.visualize_solutions() + + return epde_search_obj + + +if __name__ == "__main__": + import torch + from epde.operators.utils.default_parameter_loader import EvolutionaryParams + print(torch.cuda.is_available()) + # Operator = fitness.SolverBasedFitness # Replace by the developed PIC-based operator. + # Operator = fitness.PIC + Operator = fitness.L2LRFitness + params = EvolutionaryParams() + operator_params = params.get_default_params_for_operator('DiscrepancyBasedFitnessWithCV') #{"penalty_coeff": 0.2, "pinn_loss_mult": 1e4} + print('operator_params ', operator_params) + fit_operator = prepare_suboperators(Operator(list(operator_params.keys())), operator_params) + + aizawa_discovery(0) diff --git a/projects/pic/data/apoptosis/apoptosis.npz b/projects/pic/data/apoptosis/apoptosis.npz new file mode 100644 index 0000000000000000000000000000000000000000..f0214dd35ed6af1eef794568a6cc794b54404348 GIT binary patch literal 9126 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 bool: + metaparams = {('sparsity', var): {'optimizable': False, 'value': 1E-6} for var in all_vars} + + correct_eq = translate_equation(correct_symbolic, search_obj.pool, all_vars=all_vars) + for var in all_vars: + correct_eq.vals[var].main_var_to_explain = var + correct_eq.vals[var].metaparameters = metaparams + print(correct_eq.text_form) + + incorrect_eq = translate_equation(eq_incorrect_symbolic, search_obj.pool, + all_vars=all_vars) # , all_vars = ['u', 'v']) + for var in all_vars: + incorrect_eq.vals[var].main_var_to_explain = var + incorrect_eq.vals[var].metaparameters = metaparams + print(incorrect_eq.text_form) + + fit_operator.apply(correct_eq, {}) + fit_operator.apply(incorrect_eq, {}) + print([[correct_eq.vals[var].fitness_value, incorrect_eq.vals[var].fitness_value] for var in all_vars]) + print([[correct_eq.vals[var].coefficients_stability, incorrect_eq.vals[var].coefficients_stability] for var in + all_vars]) + print([[correct_eq.vals[var].aic, incorrect_eq.vals[var].aic] for var in all_vars]) + + # print([correct_eq.vals[var].coefficients_stability < incorrect_eq.vals[var].coefficients_stability for var in all_vars]) + return all([correct_eq.vals[var].coefficients_stability < incorrect_eq.vals[var].coefficients_stability for var in + all_vars]) + + +def prepare_suboperators(fitness_operator: CompoundOperator, operator_params: dict) -> CompoundOperator: + sparsity = LASSOSparsity() + coeff_calc = LinRegBasedCoeffsEquation() + + # sparsity = map_operator_between_levels(sparsity, 'gene level', 'chromosome level') + # coeff_calc = map_operator_between_levels(coeff_calc, 'gene level', 'chromosome level') + + fitness_operator.set_suboperators({'sparsity': sparsity, + 'coeff_calc': coeff_calc}) + fitness_cond = lambda x: not getattr(x, 'fitness_calculated') + fitness_operator.params = operator_params + fitness_operator = map_operator_between_levels(fitness_operator, 'gene level', 'chromosome level', + objective_condition=fitness_cond) + return fitness_operator + + +def apoptosis_discovery(noise_level): + data_file = os.path.join(os.path.dirname(__file__), 'apoptosis.npz') + data = np.load(data_file) + t = data['t'] + u = data['u'] + + x = u[..., 0] + y = u[..., 1] + z = u[..., 2] + dimensionality = x.ndim - 1 + + trig_tokens = TrigonometricTokens(freq=(2 - 1e-8, 2 + 1e-8), + dimensionality=dimensionality) + grid_tokens = GridTokens(['x_0', ], dimensionality=dimensionality, max_power=2) + + epde_search_obj = EpdeSearch(use_solver=False, multiobjective_mode=True, use_pic=True, boundary=15, + coordinate_tensors=(t,), verbose_params={'show_iter_idx': True}, + device='cuda') + + epde_search_obj.set_preprocessor(default_preprocessor_type='FD', + preprocessor_kwargs={}) + + popsize = 16 + epde_search_obj.set_moeadd_params(population_size=popsize, training_epochs=50) + + factors_max_number = {'factors_num': [1, 2], 'probas' : [0.8, 0.2]} + + epde_search_obj.fit(data=[x, y, z], variable_names=['x', 'y', 'z'], max_deriv_order=(1,), + equation_terms_max_number=7, data_fun_pow=1, additional_tokens=[], + equation_factors_max_number=factors_max_number, + eq_sparsity_interval=(1e-8, 1e-0)) # + + epde_search_obj.equations(only_print=True, num=1) + epde_search_obj.visualize_solutions() + + return epde_search_obj + + +if __name__ == "__main__": + import torch + from epde.operators.utils.default_parameter_loader import EvolutionaryParams + print(torch.cuda.is_available()) + # Operator = fitness.SolverBasedFitness # Replace by the developed PIC-based operator. + # Operator = fitness.PIC + Operator = fitness.L2LRFitness + params = EvolutionaryParams() + operator_params = params.get_default_params_for_operator('DiscrepancyBasedFitnessWithCV') #{"penalty_coeff": 0.2, "pinn_loss_mult": 1e4} + print('operator_params ', operator_params) + fit_operator = prepare_suboperators(Operator(list(operator_params.keys())), operator_params) + + apoptosis_discovery(0) diff --git a/projects/pic/data/autocatalysis/autocatalysis.npz b/projects/pic/data/autocatalysis/autocatalysis.npz new file mode 100644 index 0000000000000000000000000000000000000000..bdcefb7465ff656b026ebb619c776e6e7afbb747 GIT binary patch literal 4326 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 tz<*7^3$r%icMSZK`kzyNKF|L? bool: + metaparams = {('sparsity', var): {'optimizable': False, 'value': 1E-6} for var in all_vars} + + correct_eq = translate_equation(correct_symbolic, search_obj.pool, all_vars=all_vars) + for var in all_vars: + correct_eq.vals[var].main_var_to_explain = var + correct_eq.vals[var].metaparameters = metaparams + print(correct_eq.text_form) + + incorrect_eq = translate_equation(eq_incorrect_symbolic, search_obj.pool, + all_vars=all_vars) # , all_vars = ['u', 'v']) + for var in all_vars: + incorrect_eq.vals[var].main_var_to_explain = var + incorrect_eq.vals[var].metaparameters = metaparams + print(incorrect_eq.text_form) + + fit_operator.apply(correct_eq, {}) + fit_operator.apply(incorrect_eq, {}) + print([[correct_eq.vals[var].fitness_value, incorrect_eq.vals[var].fitness_value] for var in all_vars]) + print([[correct_eq.vals[var].coefficients_stability, incorrect_eq.vals[var].coefficients_stability] for var in + all_vars]) + print([[correct_eq.vals[var].aic, incorrect_eq.vals[var].aic] for var in all_vars]) + + # print([correct_eq.vals[var].coefficients_stability < incorrect_eq.vals[var].coefficients_stability for var in all_vars]) + return all([correct_eq.vals[var].coefficients_stability < incorrect_eq.vals[var].coefficients_stability for var in + all_vars]) + + +def prepare_suboperators(fitness_operator: CompoundOperator, operator_params: dict) -> CompoundOperator: + sparsity = LASSOSparsity() + coeff_calc = LinRegBasedCoeffsEquation() + + # sparsity = map_operator_between_levels(sparsity, 'gene level', 'chromosome level') + # coeff_calc = map_operator_between_levels(coeff_calc, 'gene level', 'chromosome level') + + fitness_operator.set_suboperators({'sparsity': sparsity, + 'coeff_calc': coeff_calc}) + fitness_cond = lambda x: not getattr(x, 'fitness_calculated') + fitness_operator.params = operator_params + fitness_operator = map_operator_between_levels(fitness_operator, 'gene level', 'chromosome level', + objective_condition=fitness_cond) + return fitness_operator + + +def autocatalysis_gs_discovery(noise_level): + data_file = os.path.join(os.path.dirname(__file__), 'autocatalytic-gene-switching.npz') + data = np.load(data_file) + t = data['t'] + u = data['u'] + + u = u[..., 0] + dimensionality = u.ndim - 1 + + trig_tokens = TrigonometricTokens(freq=(2 - 1e-8, 2 + 1e-8), + dimensionality=dimensionality) + grid_tokens = GridTokens(['x_0', ], dimensionality=dimensionality, max_power=2) + + epde_search_obj = EpdeSearch(use_solver=False, multiobjective_mode=True, use_pic=True, boundary=15, + coordinate_tensors=(t,), verbose_params={'show_iter_idx': True}, + device='cuda') + + epde_search_obj.set_preprocessor(default_preprocessor_type='FD', + preprocessor_kwargs={}) + + popsize = 16 + epde_search_obj.set_moeadd_params(population_size=popsize, training_epochs=2) + + factors_max_number = {'factors_num': [1, 2], 'probas' : [0.8, 0.2]} + + epde_search_obj.fit(data=[u], variable_names=['u'], max_deriv_order=(3,), + equation_terms_max_number=7, data_fun_pow=3, additional_tokens=[], + equation_factors_max_number=factors_max_number, + eq_sparsity_interval=(1e-8, 1e-0)) # + + epde_search_obj.equations(only_print=True, num=1) + epde_search_obj.visualize_solutions() + + return epde_search_obj + +def autocatalysis_discovery(noise_level): + data_file = os.path.join(os.path.dirname(__file__), 'autocatalysis.npz') + data = np.load(data_file) + t = data['t'] + u = data['u'] + + u = u[..., 0] + dimensionality = u.ndim - 1 + + trig_tokens = TrigonometricTokens(freq=(2 - 1e-8, 2 + 1e-8), + dimensionality=dimensionality) + grid_tokens = GridTokens(['x_0', ], dimensionality=dimensionality, max_power=2) + + epde_search_obj = EpdeSearch(use_solver=False, multiobjective_mode=True, use_pic=True, boundary=15, + coordinate_tensors=(t,), verbose_params={'show_iter_idx': True}, + device='cuda') + + epde_search_obj.set_preprocessor(default_preprocessor_type='FD', + preprocessor_kwargs={}) + + popsize = 16 + epde_search_obj.set_moeadd_params(population_size=popsize, training_epochs=2) + + factors_max_number = {'factors_num': [1, 2], 'probas' : [0.8, 0.2]} + + epde_search_obj.fit(data=[u], variable_names=['u'], max_deriv_order=(3,), + equation_terms_max_number=7, data_fun_pow=3, additional_tokens=[], + equation_factors_max_number=factors_max_number, + eq_sparsity_interval=(1e-8, 1e-0)) # + + epde_search_obj.equations(only_print=True, num=1) + epde_search_obj.visualize_solutions() + + return epde_search_obj + + +if __name__ == "__main__": + import torch + from epde.operators.utils.default_parameter_loader import EvolutionaryParams + print(torch.cuda.is_available()) + # Operator = fitness.SolverBasedFitness # Replace by the developed PIC-based operator. + # Operator = fitness.PIC + Operator = fitness.L2LRFitness + params = EvolutionaryParams() + operator_params = params.get_default_params_for_operator('DiscrepancyBasedFitnessWithCV') #{"penalty_coeff": 0.2, "pinn_loss_mult": 1e4} + print('operator_params ', operator_params) + fit_operator = prepare_suboperators(Operator(list(operator_params.keys())), operator_params) + + # autocatalysis_discovery(0) + autocatalysis_gs_discovery(0) diff --git a/projects/pic/data/autocatalysis/autocatalytic-gene-switching.npz b/projects/pic/data/autocatalysis/autocatalytic-gene-switching.npz new file mode 100644 index 0000000000000000000000000000000000000000..1b74e9d5ea096663be55474fca1086dcab3015d8 GIT binary patch literal 4326 zcmd6rcTiN>w#J(r1c?$wnjq-NC>RhG{PqYq7*J5eh=PEqs30I&#TiB&6{MTgA|jH5 z0xAM3I0TU#x*J46M8*IT91#O5;_If*t(v)2@1OVTR^8Lpb$W-j_FlhlomHo|-5L>5 zWt!m9q5bh`WRI~JjmH0q(UfU^a}Rk33DM*yJNT3M1NWyRWN{ z;ZnMx`5rSvBf8-pFCRZ2mqWX}eB9jm{VQD#_`37@efPR}yYt$!Ocog%%{HPRrT?EV zX~8aNw8hR@8Lo4$LvXLahN$tN*y=i}&4zU#zo(t*e~iiL3Qwu=UMxRwRMXhuIW_Mk z)`lC3)m`qR=D)_)MD`n(!9mLI4HkA<@GRa^_9IyOO0jp3H;mudHtA%eJM$S(K8yd`CcK!>Sh_h47pm4vB85T_iO9KI$;1f1mw zAC9TBJQcS*DY_@Cmh)PkieG`02=OCQpg1UyJ63$b6(}BxWHP*yiWC<`QpQumUrveQ zqeTATyJIO)oRmnp9X~*2ikC8}jQ-_CORh4-O_|*1!M0p9jp8?rJWwB=QFLz_#ZiS! z;<8bpc&d=P%bR&9R4A^hq=CQuc2$b6Drua_UqO}PtVWvmek%BIP>tfPMp|=?cwK4~ zcXh(yWn)*EI>ld|2=HrAI%tq~KfXr|N)HXv!QZZyCZ&redCa4km7z)Lqe&*y*?2mo z({%ELr$*%M>6Bj6$y0uIS!z+bX^}}e8?`9?W)Obz_idU%={SQt=ZDubgVIx*ylmkI zpiSwjO$3jNHl;6}y#L|sf=&xMr8AuznFK>G$}HXF|5IqC$FoN`w!57X60T*pz~=!#8ktO3+H*h?{V_u+YRYr4T=M?&ioA+`=KDu}4EiMQAFUH9?At z@lY-&ZR7P4q{%hS^Gv^u;mbKW_0f0GKecyRUg%xCmg9=qIhVp=>G`vL%gdm7SLari z+CA*p{50$S@I4%*FWa*J-XB=h9;oVST@HE08rq5Ma+I!g98Q^10e>^*aF=HVZdA_t zWKmy%V6S4+PJ>F6Egw~`I8lk<%&rNcu1cg9ac||$uR=8$@@_m`g^zC@KR(=Dg&rgG z8QI47p%>L%dF+?_fHPNhf^#2pA4NLN*R95(X&ZeEysM%A>L}+$X*H%QuTKq-e1Ou~k3#84`)9Kou%W?hCeyV6 z5z4fanTZYP`1mwQzr6u=opMgHvW*aTjLUPkYJ|Dvi9IGEjc}fqzufP7BdljH?lkFZ z#HL7g&B5tSxZgp-=h!#ln2uNQq2HRY>ypl;CAXV!W%mKj`JpE4Ul^z2r`?QTTH%J* z_RYv`H`)2_*Jd2-+)iWOYQ|d2zLEM@y!NV)rs_7=Js8sB~l5Nw94}VHKo!r%m2jP+{ z&HY=kea6(4jpte+ANywA;?!0&9AnxZyWI-I*uL2*Ev?x1c2Q00%T{b!aYQuyYb#U~ zQc5iq*vL5b^ucojHYCOl4!D`Kv2M=Iff{=@B0G1Vll5T3bol2e(@-|P+&g;HlEFr~ zdrtnM3^o{_%$CXCW#d?Dv{XY28~#5?)_c8RLqtx7K0e0A=$_yLZ&?nAW7v#l9S*|g zyGlgBVA-&GUn~Q;Mq9MnB<-&2L!kVZ!E;xpb=`q<{tedMec&?O- zV>?%$4Qk<{V|K!aZGUo+m^u(|@_~zx=Fd;RNwmRX;IL`AMjIYge3uORu??%o_Lk2g zZ7|%NQr)tl4c(fr!VbB&A;7wRt5{$gw*Bc?9CM}(QL36WtxIhvZi|me%5Q`BT-7@D z$~OG7%x-Jw;XZ1hrs;;)bjXYPo$F`m&P+mP?ZOSZ0t%dof(CQ-G zS}NzZNVEvTy{|pb+TOx@g<$`?$%W8#@n}@~b`#SNNac^X+(hYbLe1sPH}EiMqwd&> z8`x9b<)(4(ItuOGb{FD09%^h--BDA3OQMILd0(BtYslO6{oVVDt8jQTt0hV4Dh^+ZQo4E|7oV5L$rm=}peRCYkHdl-SU#1I zGfB?I%83){?-jH0=|_pf>(6FEw$Jy@N69R7|CU#nb~Y0+J@b~UDP_XeMMY2fat1aS zhm7rBtqgc_X_c9cdanq~*ubu)RtAZDM>HdUS@0&n!*DPp1#wzy2&0 z4o59Ahkj2*jAz5Bs%a{s#LMgzU#8%J@aw+PxD+_db*6t=or1B>zxuBUr9gk--sG}7 zS5W(W{Z~2vD=3&FwmD+K6{PI_!AW~G8R0CFT~L?|6^msrjRKP)Ddb%gW|0iqdB-~! zM3d3KeyU$n!ey|Al-%>?U50$Y%&MZAB%FS@{6y{UBp9`kkq5LS^p~9-h`VwLtUl*! zGpsM6d}K+e^d<&3>M=Q<#N*M$?;|#&|>- zeK&Gs#Y1h{9y>?(c(iq9=3iM5k6Q1g<8wvgaW1{w;sZAhY8{zPgXwXg&u({;4vNFG z0gpMWY~v94QvxelHx8v4@2sRg$0F?GMy1f!ShNMT8@$Vk#as9LJ#N47?n{~;HSLIn zO~9HL24=Bv9@5d+uN;dliK9;#BQe-}ydj6x5(7zkfJsnZ493e9ZRbYDz^YDG{N|w; zOrLLaUf3=MZpt=RYV%{jI?|Z=mvRiY+_CowABzU7|F7emC()3vDXwj=h{jOg?LB8Q zqwy#pFJ>qr8f)iWyE^64-~(l2W}n6NYPyOCGLM0hYQLbrg4&~mqSol8u}uMHN;Im5(#?u*MM zflScMUu{|A!Nik#%V)bBnaC=9|8>-wiE@XEU&TzBcy=?#nQ6cqKfU!FOO*-ZqcQR- zQcOHLQ6?_+m4Q|v$9S({20|-;|NQea1_~_8eWJJwg!wFV{JDmKXW>jy&$|p9Fq!t@ zb3Oy(E7Y&QOJl%z=uPFu1O}o`)s$;SG2kbCaP6v-JU?2)id~@$oZev5*y78;UZ2dp85KL?Z%5w=vMqHu&bTk%7Xx-pnOy8L*Mf_Vlo3z zxMmEz7uEHUTfpGeSs`I%k^k-7;9q~vX|ymQ{_n|Cgnu{r=R5GZpU#R-|zQ2u5+%-WtEUH5w}oi zIPCY}ULz44j`tP85plr=egRkwYl2=NrGj7R^t`H}J=skoAG%N=B|N{CRC?^%=TI z>75S#{4TWZR%O3uPY-`xKVtl_;;!KE*AF6#c0$uin<2iPVPrGZv5D+G!nZeyoMvB^ zHAj!}?fypVBoffm+;P7B3ACB)ogZE^$@epbcB)zGIL%J;{mr612Kd`VjXAzw9GEBv zUErkRU?E-sSbo30xVk_97WN?sywxwu1YsdAA%Lg3=J)G`U}0avU^j0-7#4650mq_q zkNt&2VPT)5;5I(@gEjAWaCfu_7WTae*3Wl{!vfCY;99lp`l59bun>m?Y@G*8 z5*BcigzdjXrM?AA!9rY8;O=#Z>vAzH@E{GldVaF*CP~9WoH8&!{8$DSaFzugIHn%5 z{J3S|uUCuZGrMH@@#CRTfEN*-j{_cF*z*X-^YI|Se0Y%rJ}w0Ko6AOsj2s^yIly@4 z66N?f$pPDi7a)<37ZKiEKGj zf3g(#_$$By{EB=Y6yaMi&!Qrq4@LOS+pe+_pBE*l<)WGXK#9+f63ollXbGREB~Zs@ zBj))MK3_|qfu}AzWj=4pFi+<{%6$G*fG7TrO)7jIRiK3z-Y*qCpQ_MS!3#i@&#Njd zTrR47eyK2`8)f{%ipuAi3Ww(bq4N2r!q`C`BUC=`R5-%5E-FRi^G^dFT5J~@Uk5ap zzud3V_8rHNIZdV1}Db6#CTo zx={ySwy0XB&exAR%wOIi>Ur*&(Y&kdWN%$n^AR3VTbSCHuS8HmhB+g zfdn_acDz^Bfd;?D`tCa1iLM+w`8RoS7h@ZWuC|KvGo)4G1NXI=8k(aHg|)bHo+$b%eo zQE}^I+zbab*_(f!N*qK?{81lQyCJkg-DdyF*&$@MJt{N)pJ5dChk~Jh&Is~&f7q}y zdlc!cPI&pXVhp|RG(ROy97iU{x;;*unLznDs&71XrqFUf<&YW488j}Y)0%8Lhgyn{ zOLx@b;7x;-aGoPKoldP(XSWGKtX}<$$8!;=Dp{_Gy%vKDhnsd>oEC?zR#gN=FDa&-fup`A1Zd`rMmeR(!Ka{>i(S$rXg%6>qvbdmjGdxy+H_N3 z)>#2NyK3&hdYC{1df}z;4Ma6?$E|Nqh*bloh9~K;usX<&=WEFXsl&kwhc2A` zsSXP1t^re48t~+=j<-ok8c^2sV@d3w25egFc1*-h6RNEA&gsW$LZ5H6qG`1z#Frd* zUMH^w77v%bNZ+IdhlfY~4#j9enab^unm1Yy)6@J#Vpt0VW&&!BRq2p_ zKRw?%;I#oCsCxC3vNqH^mo_@mwc)l^fAbRqZFr>I7x2wk8+_^&w7;5aL-X;<y4x~ zJXD|y&YgATQC-t`@jpabbhl26Ql z7VOyhRPINW7C5OTwQerZf>lLdd?b^!K=|*OvYBu#=wMgW8t>GCwbnYLxmH@xcKPGm z2xToWyJ=T`ds-8QDn@LHKQ&>+hudPUg_@vDaLLDC!daOu zGQB#QaPzYDrpLmX@Tsb-zotP0uGAfg2q@Hm3oAPJ9FEt3K=D)Vo%=LEwq|)psf`9$ ze%x%QsGtF@g{^+I16;irwJV6dQ-_o75t;XHs)O3ug3inUb@2Qisi5MZ4(w|Q+a~4J zL0I?MUaMX;2pj9Z-TF!mOmnQ~dgIg}XmwP@hFxmVSo>ll-dGJTCgeO(7g7U%>C&(> zUuY1Pm-kZtE)B>t-#X>}Y4GYDEzH`I2IqF(dv#?I4G8kd4fwCz+e4nC#-rO*P$f!# zclDtH#KH)O+VOE8x4x z-PqX<3pg(LamW7C7ErOak9t?t0%T_GBXVCZ2hsHW_&m4eP(L}yE}AfhGo^v$9XHG& zYU|a;%`41dZsmBEL4z5b_Sf8b^OPAV+>;Hf)HVY@(%Q!|m8MV^y=PZSxG89wn}#%K zn}YE}gOjGUCg40RnW}rq1TH0uif(o=fwsHFoBah%fad6&>hzWY`sLBY@)!n;$(>?M zxidf{Osw|fat6pYr3dLKGoTH~U?z{-O*xXqH;Fm&gFh&$S`R zdJGs;{zmXw%YaLBcNvqR3@CB^k{0@i0n;12TEyBI@a9T3p<2TP%4c`}ruv$|uCm^K zvwRaMbJ=8cOwbhkUp~6%>1qn{Pu64mGfY9*S>lYofEj#Jjd|g?-3)HwGxzT*FaxhO zE-y_;=1^v78+jtw91NaXO$dH72ihaM_*m2B(4U(WKa#i{9(H;k7n@!VF4YGNQr#^; zYqDcida(sq3&uVhrLTazWue6Z(JSCi)loIv;0ov*nWA0VWC6-cI&3{I}JhI6XXx#3yXkQkI}KP!!Z+V!DfIsn1a5|Z?R zF9^m%O7^EH0Gz$D{aoQn0O8nM_rk&eMAkML{7MJNk|_V)`W-;s!|hI_s12;HI{t{w zumKeOY-Q{&8|WXeo$HUa0ew2@Slvq-DE!oX-nGjH`VuDOTuHWY`I#r925ceU*pA$= z-xi`7C+BuuvxPw=U-vz1Tex>rC-+yEEl_3)PktfT!QQGqFN+m+Fl4e`fVA5V6zxan z+%MX}U~={80!E6d8dE5XF0%~@XFGW4-T+%)Ux|~D|cK93l_sU!b7)|+b0zr zA^)25b$f;*u)l>|Si8m%hDtM%lD!;3RnV>5@VFz0dNsreU2%k!Q-!z4nT{Yyq!GO_ zM;KlCZg{B55uEaG=ofZ4g7LlXhxwC^pqZ~`*)GKd0abxaXB8$Sd*<+4i=6UH-2 z9=`dD2{Ir42ouaV5MAX*i+>KsM2&a%x-8kHgSYN-zJ7BHIBkFmG@Yic64x+~Hq1j;42y*c$fAX$*3^fjL`h|=qkxTp?M+NdM z3bmSx>Two;C8bt@+iHX$w0t5#DN_{euLjP2&k_fD@@b_V#ZqAJwVX0qDg({q$4k4` z;NjK4;xfNz0+8#OI^$R5;K0gn;^0Yy-saYwLjyz*uF3d$*^UGyol$-aPZAuXi>BDF zOS|(BaHhu-4%Dk zO-vjDCZ7fFP8Ng2-IV4?OHuF$AB%`=6NbXlx7q`V+<3#nHECM}!6`8-Y9mzu;_d}A z?7QYr9Hl)?A$2u@GKjKY7md{#b5P0>HFB|jAM#LQ20fv4BWm$7ZK_5Gm$xRj_v>3xqj|)cO?R3Q z%XoDAFu4J-hnsI!>u`0D;a6N0{1xGJ;_r8KS0L6@p-{?(_Xsz4Rc)*JD}+l?nAMkh zjBt?PbuI2s441oa_lkRuG2Cin!nMek7_L4d-?p*@V-=l=GADn;SVAw|TP}XaAT4rr z_P#0%M@^5=(W%DR2Ts;Bm(^f6se3=YTz+D>H|1%%MfDh~CM{r$-!6=0xGSekzZ;`Y+&HqaryFD6*yY}MuLpz91IirNUM%ItIH^UT z4{N;s%$%Ibt$!q`G_UE$*kXM}$$kA8m_MyazA%6pndaY8#B(s**WjQZ+d0^0tq~2= zG!DiN3BtSfaWIcWuY+!kL5$r-u=GDVh{;MV*;0rNVyq>miQ9#TFpkLYnG;S!SR8$| zp;I)sz2lFA^%X-{K*jkUFS%jNB`T?0e9JIKt!DWQB@JT%e%?o&e+^@}=YQ8&X^ddm z*#{IS_l;mic|EC5vqvxvHhw35d<4UphkI4qjABNk(bEOdqnO9Ga7u73w+^}&xLSJ* z3s{fWzYsQtQT-%-hJG5u;tcf;9n$!X1w2=Gtvtc4-<>tZ)&Itf{KMP*(KzN(Z0(!! zU>uX(Vx~uvo4`CaUa`De(3-_P89~;JO^!0Pm=IzL&rr*V2s((CvC-^kvx}4jpvS zX+8qxXup()e)LBw%(j+{}MLUAoq8$G@Yz%ARXb32Pfz_McHgB|AL zkTBL+`pZQM{y5d;%#o0Rr*+0>mKVr^RY$1Y^lLoCTMYKiWDr0jOdN-TR~5hFR!_fM_+ z#38_LCG+gKrvwO!{uO0^kN~dtfB((VA%LKpVc*R*JWNEUF;=JG;qj!;GBGzitn0Zh z_Jx3l8xqG4a$>|Qa51F%I$d133_La?NZ=KK;ZARsXtdq zK)T|`(8*MB$o_m%xqEsMI912*)!n!VcK9gYEqEvf+D~&1T$ zr#;IPa8Pr*=BRDz913X2KKfjB7CDL7*5_TDM%jPe*(@$Th0bffCB}qIpisypX~D`kEs2^aoJ3%H-yOf*xcf(V_X3 z(TQ-6<8vkNx1zXk)xH9OCe(;?K3jI@2V&i#T$EL)LTncik-pk@2)8;}&gXp=!kq#W zjl>)bkd(z*tx^mo^Zf;ezGBn{%dM9q>oFsycXgU>3)bj}KOURYfpOkG5*c>u#avbi z2oi< bool: + metaparams = {('sparsity', var): {'optimizable': False, 'value': 1E-6} for var in all_vars} + + correct_eq = translate_equation(correct_symbolic, search_obj.pool, all_vars=all_vars) + for var in all_vars: + correct_eq.vals[var].main_var_to_explain = var + correct_eq.vals[var].metaparameters = metaparams + print(correct_eq.text_form) + + incorrect_eq = translate_equation(eq_incorrect_symbolic, search_obj.pool, + all_vars=all_vars) # , all_vars = ['u', 'v']) + for var in all_vars: + incorrect_eq.vals[var].main_var_to_explain = var + incorrect_eq.vals[var].metaparameters = metaparams + print(incorrect_eq.text_form) + + fit_operator.apply(correct_eq, {}) + fit_operator.apply(incorrect_eq, {}) + print([[correct_eq.vals[var].fitness_value, incorrect_eq.vals[var].fitness_value] for var in all_vars]) + print([[correct_eq.vals[var].coefficients_stability, incorrect_eq.vals[var].coefficients_stability] for var in + all_vars]) + print([[correct_eq.vals[var].aic, incorrect_eq.vals[var].aic] for var in all_vars]) + + # print([correct_eq.vals[var].coefficients_stability < incorrect_eq.vals[var].coefficients_stability for var in all_vars]) + return all([correct_eq.vals[var].coefficients_stability < incorrect_eq.vals[var].coefficients_stability for var in + all_vars]) + + +def prepare_suboperators(fitness_operator: CompoundOperator, operator_params: dict) -> CompoundOperator: + sparsity = LASSOSparsity() + coeff_calc = LinRegBasedCoeffsEquation() + + # sparsity = map_operator_between_levels(sparsity, 'gene level', 'chromosome level') + # coeff_calc = map_operator_between_levels(coeff_calc, 'gene level', 'chromosome level') + + fitness_operator.set_suboperators({'sparsity': sparsity, + 'coeff_calc': coeff_calc}) + fitness_cond = lambda x: not getattr(x, 'fitness_calculated') + fitness_operator.params = operator_params + fitness_operator = map_operator_between_levels(fitness_operator, 'gene level', 'chromosome level', + objective_condition=fitness_cond) + return fitness_operator + + +def bacterial_respiration_discovery(noise_level): + data_file = os.path.join(os.path.dirname(__file__), 'bacterial-respiration.npz') + data = np.load(data_file) + t = data['t'] + u = data['u'] + + x = u[..., 0] + y = u[..., 1] + dimensionality = x.ndim - 1 + + trig_tokens = TrigonometricTokens(freq=(2 - 1e-8, 2 + 1e-8), + dimensionality=dimensionality) + grid_tokens = GridTokens(['x_0', ], dimensionality=dimensionality, max_power=2) + + epde_search_obj = EpdeSearch(use_solver=False, multiobjective_mode=True, use_pic=True, boundary=15, + coordinate_tensors=(t,), verbose_params={'show_iter_idx': True}, + device='cuda') + + epde_search_obj.set_preprocessor(default_preprocessor_type='FD', + preprocessor_kwargs={}) + + popsize = 16 + epde_search_obj.set_moeadd_params(population_size=popsize, training_epochs=50) + + factors_max_number = {'factors_num': [1, 2], 'probas' : [0.8, 0.2]} + + epde_search_obj.fit(data=[x, y], variable_names=['x', 'y'], max_deriv_order=(1,), + equation_terms_max_number=7, data_fun_pow=1, additional_tokens=[], + equation_factors_max_number=factors_max_number, + eq_sparsity_interval=(1e-8, 1e-0)) # + + epde_search_obj.equations(only_print=True, num=1) + epde_search_obj.visualize_solutions() + + return epde_search_obj + + +if __name__ == "__main__": + import torch + from epde.operators.utils.default_parameter_loader import EvolutionaryParams + print(torch.cuda.is_available()) + # Operator = fitness.SolverBasedFitness # Replace by the developed PIC-based operator. + # Operator = fitness.PIC + Operator = fitness.L2LRFitness + params = EvolutionaryParams() + operator_params = params.get_default_params_for_operator('DiscrepancyBasedFitnessWithCV') #{"penalty_coeff": 0.2, "pinn_loss_mult": 1e4} + print('operator_params ', operator_params) + fit_operator = prepare_suboperators(Operator(list(operator_params.keys())), operator_params) + + bacterial_respiration_discovery(0) diff --git a/projects/pic/data/ns/ns.py b/projects/pic/data/ns/ns.py index 081fb59a..0bdfffff 100644 --- a/projects/pic/data/ns/ns.py +++ b/projects/pic/data/ns/ns.py @@ -144,7 +144,7 @@ def ns_discovery(foldername, noise_level): # dimensionality = data.ndim - 1 - epde_search_obj = EpdeSearch(use_solver=False, multiobjective_mode=True, + epde_search_obj = EpdeSearch(use_solver=True, multiobjective_mode=True, use_pic=True, boundary=[21, 21, 46], coordinate_tensors=grid, device='cuda') @@ -155,7 +155,7 @@ def ns_discovery(foldername, noise_level): popsize = 64 epde_search_obj.set_moeadd_params(population_size=popsize, - training_epochs=15) + training_epochs=1) custom_grid_tokens = CacheStoredTokens(token_type='grid', token_labels=['t', 'x'], From 21dbd34ab06c7205008ff7628e8c5d157bafb315 Mon Sep 17 00:00:00 2001 From: Gromwud Date: Wed, 20 May 2026 12:12:52 +0300 Subject: [PATCH 2/8] moeadd: faster ndl_update + bounded TFPool + quieter prints ndl_update replaces the per-call deepcopy(levels) with a shallow [list(lvl) for lvl in levels]; only the inner level lists are mutated (append, slice), so cloning every SoEq/Term/Factor on each individual inserted into the Pareto layers was pure overhead. Also hoists the two check_dominance comprehensions out of the per-moving-set-element branch so each direction is computed once instead of up to four times. TFPool.create_with_var converts the bare while True over family sampling into a bounded for-loop that raises RuntimeError on pool exhaustion (was a silent crash or spin when families.remove(family) ran against an already-removed entry). MOEADD's per-call print statements in marriageSolutionAssignment, ParetoLevels.set_weights, and the obj_fun length probe are gated behind global_var.verbose.show_iter_idx or converted to warnings.warn so a default run no longer floods stdout with debug arrays. --- epde/interface/token_family.py | 21 +++++-- epde/optimizers/moeadd/moeadd.py | 76 +++++++++++++++++++------ epde/optimizers/moeadd/supplementary.py | 49 ++++++++++------ 3 files changed, 107 insertions(+), 39 deletions(-) diff --git a/epde/interface/token_family.py b/epde/interface/token_family.py index a56c7867..17504185 100644 --- a/epde/interface/token_family.py +++ b/epde/interface/token_family.py @@ -557,15 +557,26 @@ def create_with_var(self, variable: str, token_status=None, **kwargs): assert variable is not None, 'Can not create token with a specific variable for ' families = [f for f in self.families if variable == f.variable] - while True: + max_iter = len(families) + 1 + family = None + for _ in range(max_iter): + if not families: + raise RuntimeError( + f"TFPool.create_with_var: no family can produce a token for variable={variable!r}" + ) try: probabilities = np.array([len(f.tokens) for f in families]) - family = np.random.choice(families, p = probabilities/probabilities.sum()) - return family.create(label=None, token_status=token_status, - all_vars = [family.variable for family in self.families_demand_equation], + family = np.random.choice(families, p=probabilities/probabilities.sum()) + return family.create(label=None, token_status=token_status, + all_vars=[fam.variable for fam in self.families_demand_equation], **kwargs) except ValueError: - families.remove(family) + if family is not None and family in families: + families.remove(family) + family = None + raise RuntimeError( + f"TFPool.create_with_var: exhausted {max_iter} attempts for variable={variable!r}" + ) def __add__(self, other): return TFPool(families=self.families + other.families) diff --git a/epde/optimizers/moeadd/moeadd.py b/epde/optimizers/moeadd/moeadd.py index bfbf33af..d0c01ad2 100644 --- a/epde/optimizers/moeadd/moeadd.py +++ b/epde/optimizers/moeadd/moeadd.py @@ -106,13 +106,15 @@ def marriageSolutionAssignment(weights: np.ndarray, solutions: List[MOEADDSoluti w_preferences[i] = np.roll(w_preferences[i], shift = -1, axis = 0) w_preferences[i, -1] = -1 - print('acute_angles\n', acute_angles) - print('matches\n', matches) + if global_var.verbose.show_iter_idx: + print('acute_angles\n', acute_angles) + print('matches\n', matches) checkWeightAssignmentUniqueness(matches) for sol_idx, solution in enumerate(solutions): weight_idx = np.where(matches[:, sol_idx] == 1)[0][0] - print(f'Assigned weight {weight_idx} for {sol_idx}') + if global_var.verbose.show_iter_idx: + print(f'Assigned weight {weight_idx} for {sol_idx}') solution.set_domain(weight_idx) @@ -251,11 +253,11 @@ def delete_point(self, point): """ new_levels = [] population_cleared = [] - point_system = point.terms_labels + point_system = point.equations_labels for level in self.levels: temp = [] for element in level: - if element.terms_labels != point_system: + if element.equations_labels != point_system: temp.append(element) population_cleared.append(element) if not len(temp) == 0: @@ -301,7 +303,10 @@ def get_by_complexity(self, complexity): def set_weights(self, weights): if weights is None: - print(f'Setting ParetoLevels attribule weights with None: this should be a placeholder, expect futher logs.') + warnings.warn( + "Setting ParetoLevels.weights to None: this should be a placeholder; " + "expect further logs." + ) #if neccessary, implement additional logic into setter self._weights = weights @@ -466,6 +471,10 @@ def __init__(self, population_instruct, pop_size, solution_params, self.best_obj = best_sol_vals self._hist = [] + # Per-epoch Pareto-level-0 snapshots populated during ``optimize``. + # Each entry is a list of ``{'text_form', 'obj_fun'}`` dicts -- one + # per solution on the non-dominated front at the end of that epoch. + self._pareto_history = [] def abbreviated_search(self, population, sorting_method, update_method): """ @@ -574,7 +583,8 @@ def pass_best_objectives(self, *args) -> None: None """ if len(self.pareto_levels.population) != 0: - print('comparing lengths', len(args), len(self.pareto_levels.population[0].obj_funs)) + if global_var.verbose.show_iter_idx: + print('comparing lengths', len(args), len(self.pareto_levels.population[0].obj_funs)) assert len(args) == len(self.pareto_levels.population[0].obj_funs) self.best_obj = np.empty(len(self.pareto_levels.population[0].obj_funs)) elif len(self.pareto_levels.unplaced_candidates) != 0: @@ -600,19 +610,25 @@ def set_strategy(self, strategy_director): builder.assemble(True) self.set_sector_processer(builder.processer) - def optimize(self, epochs): + def optimize(self, epochs, early_stopping_callback=None): """ - Method for the main unconstrained evolutionary optimization. Can be applied repeatedly to - the population, if the previous results are insufficient. The output of the - optimization shall be accessed with the ``optimizer.pareto_level`` object and + Method for the main unconstrained evolutionary optimization. Can be applied repeatedly to + the population, if the previous results are insufficient. The output of the + optimization shall be accessed with the ``optimizer.pareto_level`` object and its attributes ``.levels`` or ``.population``. - - Args: + + Args: epochs (`int`): Maximum number of iterations, during that the optimization will be held. - + early_stopping_callback (`callable`, optional): hook invoked at the end of every + epoch with ``(snapshot, epoch_idx)`` where ``snapshot`` is the list of + ``{'text_form': ..., 'obj_fun': ...}`` dicts for the current Pareto level 0. + Returning a truthy value terminates the optimization. Use this to plug in + domain-specific stop conditions (e.g. thesis runs that already match a known + ground-truth structure). Default ``None`` runs the full ``epochs`` budget. + Note: that if the algorithm converges to a single Pareto frontier, the optimization is stopped. - + """ if not self.abbreviated_search_executed: self.hist = [] @@ -622,18 +638,42 @@ def optimize(self, epochs): print(f'Multiobjective optimization : {epoch_idx}-th epoch.') for weight_idx in np.arange(len(self.weights)): if global_var.verbose.show_iter_idx: - print(f'During MO : processing {weight_idx}-th weight.') + print(f'During MO : processing {weight_idx}-th weight.') sp_kwargs = self.form_processer_args(weight_idx) - self.sector_processer.run(population_subset = self.pareto_levels, + self.sector_processer.run(population_subset = self.pareto_levels, EA_kwargs = sp_kwargs) stats = self.pareto_levels.get_stats() self._hist.append(stats) + # Snapshot the current Pareto-0 structures so consumers can + # ask "in which epoch was equation X first discovered?". + snapshot = [] + for sol in self.pareto_levels.levels[0]: + try: + obj = sol.obj_fun.tolist() if hasattr(sol.obj_fun, 'tolist') else list(sol.obj_fun) + except Exception: + obj = None + snapshot.append({'text_form': sol.text_form, 'obj_fun': obj}) + self._pareto_history.append(snapshot) if global_var.verbose.iter_fitness: print(f'\n--- Dominating Pareto front (epoch {epoch_idx}) ---') for sol_idx, solution in enumerate(self.pareto_levels.levels[0]): print(f' [{sol_idx}] obj_fun = {solution.obj_fun}') print(f' {solution.text_form}') - print('---') + print('---') + + if early_stopping_callback is not None: + try: + should_stop = bool(early_stopping_callback(snapshot, int(epoch_idx))) + except Exception as exc: + # A misbehaving callback must not abort the run; log + # and keep going so the user still gets a result. + print(f'[early_stopping_callback] raised {exc!r}; ignoring.') + should_stop = False + if should_stop: + if global_var.verbose.show_iter_idx: + print(f'Early stopping at epoch {int(epoch_idx) + 1}/' + f'{int(epochs)} (callback returned True).') + break def form_processer_args(self, cur_weight : int): # TODO: inspect the most convenient input format """ diff --git a/epde/optimizers/moeadd/supplementary.py b/epde/optimizers/moeadd/supplementary.py index d0c31681..8fa9f47d 100644 --- a/epde/optimizers/moeadd/supplementary.py +++ b/epde/optimizers/moeadd/supplementary.py @@ -84,31 +84,48 @@ def ndl_update(new_solution, levels) -> list: # efficient_ndl_update """ moving_set = {new_solution} - new_levels = deepcopy(levels) # levels# CAUSES ERRORS DUE TO DEEPCOPY + # Shallow per-level copy: ndl_update only mutates the outer list (slice + # assignment, append, extend) and the inner level lists (append, replace); + # the MOEADDSolution objects themselves are never mutated, so cloning them + # via deepcopy is pure overhead (per-call on every individual added to the + # non-dominated levels). Aliasing ``levels`` directly DOES corrupt the input + # because of the in-place slice assignment below -- the comprehension below + # gives us a fresh outer list and fresh inner lists while preserving the + # original solution-object identities. + new_levels = [list(lvl) for lvl in levels] for level_idx in np.arange(len(levels)): moving_set_new = set() for ms_idx, moving_set_elem in enumerate(moving_set): - if np.any([check_dominance(solution, moving_set_elem) for solution in new_levels[level_idx]]): + level_new = new_levels[level_idx] + # Compute each direction of dominance against the (possibly already + # mutated) new level exactly once instead of re-running the same + # list-comp inside up to three branches. + dom_over_me = [check_dominance(s, moving_set_elem) for s in level_new] + dom_by_me = [check_dominance(moving_set_elem, s) for s in level_new] + if any(dom_over_me): moving_set_new.add(moving_set_elem) - elif (not np.any([check_dominance(solution, moving_set_elem) for solution in new_levels[level_idx]]) and - not np.any([check_dominance(moving_set_elem, solution) for solution in new_levels[level_idx]])): - new_levels[level_idx].append(moving_set_elem) - elif np.all([check_dominance(moving_set_elem, solution) for solution in levels[level_idx]]): + elif not any(dom_by_me): + # Falls through from branch 1, so `not any(dom_over_me)` already holds: + # incomparable with every existing element, append to this level. + level_new.append(moving_set_elem) + elif all(check_dominance(moving_set_elem, s) for s in levels[level_idx]): + # NOTE: this branch deliberately checks the ORIGINAL ``levels`` + # snapshot, not the mutated ``new_levels``, to detect the case + # where this element dominates the entire pre-update Pareto + # layer and therefore deserves a new layer above it. temp_levels = new_levels[level_idx:] new_levels[level_idx:] = [] new_levels.append([moving_set_elem,]) - new_levels.extend(temp_levels) # ; completed_levels = True + new_levels.extend(temp_levels) else: - dominated_level_elems = [level_elem for level_elem in new_levels[level_idx] if check_dominance( - moving_set_elem, level_elem)] - non_dominated_level_elems = [ - level_elem for level_elem in new_levels[level_idx] if not check_dominance(moving_set_elem, level_elem)] - non_dominated_level_elems.append(moving_set_elem) - new_levels[level_idx] = non_dominated_level_elems - - for element in dominated_level_elems: - moving_set_new.add(element) + # Partial domination: keep non-dominated elements + me at this + # level; bump dominated elements down via moving_set_new. + new_levels[level_idx] = [le for le, dom in zip(level_new, dom_by_me) if not dom] + new_levels[level_idx].append(moving_set_elem) + for le, dom in zip(level_new, dom_by_me): + if dom: + moving_set_new.add(le) moving_set = moving_set_new if not len(moving_set): break From fa3efe57a919114d6dca856254b41cbb99606715 Mon Sep 17 00:00:00 2001 From: Gromwud Date: Wed, 20 May 2026 12:13:24 +0300 Subject: [PATCH 3/8] Equation/term mutation: bounded loops, terms_labels cache, stop-on-exhaustion Bounded the three remaining unbounded retry loops over the structure- mutation hot path -- they were the canonical "spin on a constrained pool" hazard from feedback-structure-dedup: * Equation.__init__ term-fill loop now bounded (max_iter=100) and BREAKS out of the outer slot loop on exhaustion. The pool that just refused to yield a unique signature will not become un- exhausted on the next slot, so further attempts only waste cycles or risk introducing a duplicate downstream. * Equation.add_random_term now returns bool: False when terms_number is already reached or when the 10-attempt pool sample never finds a unique signature. Callers must branch on the False to stop looping. The previous or/and inversion left the function a silent no-op; the cap (terms_number) prevents the 10x caller in EquationMutation.apply from pushing equations past the metaparameter. * TermParameterMutation.apply (singleobjective): while True -> for _ in range(100); drops the "ENTERING LOOP" / "checking presence" debug prints; warns once on exhaustion. Same shape as the multiobjective sibling. Wired the Equation.terms_labels and terms_labels_without_power properties through the already-declared _terms_labels_cache / _terms_labels_without_power_cache slots; the infrastructure existed (15 _invalidate_label_cache() call sites, slot declarations, reset hooks) but the properties recomputed unconditionally. Added invalidations at every Term-level mutation / crossover site that bypasses the Equation API: * TermMutation and TermParameterMutation in singleobjective and multiobjective mutations.py (8 invalidation points) * EquationMutation crossover + EquationExchangeCrossover in both flavors of variation.py * EquationMutation.apply (multi) now breaks the 10x add_random_term loop the moment the helper returns False, mirroring the rule "exhaustion stops further structure growth" pinned in the feedback memory. Characterization test was previously pinning the OLD (intentionally disabled) no-cache contract; replaced with two tests that pin the new behaviour -- first access populates _terms_labels_cache, second access returns the identical frozenset, _invalidate_label_cache drops it. 26/26 tests pass; LV smoke-run failure ("Equation has duplicate terms") that the previous build hit was caused by the same add_random_term overshoot and is fixed here. --- epde/operators/multiobjective/mutations.py | 85 +-- epde/operators/multiobjective/variation.py | 36 +- epde/operators/singleobjective/mutations.py | 40 +- epde/operators/singleobjective/variation.py | 12 +- epde/structure/main_structures.py | 483 +++++++++++++----- .../test_main_structures_characterization.py | 333 ++++++++++++ 6 files changed, 775 insertions(+), 214 deletions(-) create mode 100644 tests/unit/test_main_structures_characterization.py diff --git a/epde/operators/multiobjective/mutations.py b/epde/operators/multiobjective/mutations.py index 7c01ed16..a2b077fe 100644 --- a/epde/operators/multiobjective/mutations.py +++ b/epde/operators/multiobjective/mutations.py @@ -44,11 +44,11 @@ def apply(self, objective : SoEq, arguments : dict): # TODO: add setter for best altered_objective.vals.replace_gene(gene_key = eq_key, value = altered_eq) - # for param_key in params_keys: - # altered_param = self.suboperators['param_mutation'].apply(altered_objective.vals[param_key], - # subop_args['param_mutation']) - # altered_objective.vals.replace_gene(gene_key = param_key, value = altered_param) - # altered_objective.vals.pass_parametric_gene(key = param_key, value = altered_param) + for param_key in params_keys: + altered_param = self.suboperators['param_mutation'].apply(altered_objective.vals[param_key], + subop_args['param_mutation']) + altered_objective.vals.replace_gene(gene_key = param_key, value = altered_param) + altered_objective.vals.pass_parametric_gene(key = param_key, value = altered_param) return altered_objective @@ -68,7 +68,11 @@ def apply(self, objective : Equation, arguments : dict): # objective.structure[term_idx].reset_saved_state() equation = deepcopy(objective) for _ in range(10): - equation.add_random_term() + if not equation.add_random_term(): + # Either ``terms_number`` reached or the pool ran out of + # uniques. Either way, further attempts would no-op or + # risk introducing a duplicate downstream -- stop here. + break assert len(equation.terms_labels) == len(equation.structure) @@ -120,15 +124,27 @@ def apply(self, objective : tuple, arguments : dict): #term_idx, equation): """ self_args, subop_args = self.parse_suboperator_args(arguments = arguments) - temp = deepcopy(objective[1].structure[objective[0]]) - objective[1].structure[objective[0]].randomize() - objective[1].structure[objective[0]].reset_saved_state() - while (len(objective[1].terms_labels) != len(objective[1].structure) - or objective[1].structure[objective[0]].terms_labels == temp.terms_labels): - objective[1].structure[objective[0]].randomize() - objective[1].structure[objective[0]].reset_saved_state() - # print(f'CREATED DURING MUTATION: {new_term.name}, while contatining {objective[1].structure[objective[0]].descr_variable_marker}') - return objective[1].structure[objective[0]] + term_idx, equation = objective + temp = deepcopy(equation.structure[term_idx]) + equation.structure[term_idx].randomize() + equation.structure[term_idx].reset_saved_state() + equation._invalidate_label_cache() + + # Re-randomize while the mutation produced a duplicate term within + # the equation OR no actual change vs the previous term. Cap the + # retries so a tight token pool can't deadlock the optimizer (same + # hazard fixed in ``enforce_rps_uniqueness`` / ``simplify_equation``). + max_iter = 100 + for _ in range(max_iter): + signatures = {t.factors_labels for t in equation.structure} + duplicate = len(signatures) != len(equation.structure) + unchanged = equation.structure[term_idx].factors_labels == temp.factors_labels + if not (duplicate or unchanged): + break + equation.structure[term_idx].randomize() + equation.structure[term_idx].reset_saved_state() + equation._invalidate_label_cache() + return equation.structure[term_idx] def use_default_tags(self): self._tags = {'mutation', 'term level', 'exploration', 'no suboperators'} @@ -161,24 +177,18 @@ def apply(self, objective : tuple, arguments : dict): # term_idx, objective self_args, subop_args = self.parse_suboperator_args(arguments = arguments) unmutable_params = {'dim', 'power'} - # objective[1] = deepcopy(objective[1]) - while True: - # Костыль! - print('ENTERING LOOP') - try: - objective[1].target_idx - except AttributeError: - objective[1].target_idx = 0 - # - term = objective[1].structure[objective[0]] + term_idx, equation = objective + if not hasattr(equation, 'target_idx'): + equation.target_idx = 0 + + # Cap the retry loop so a constrained token pool can't deadlock + # the optimizer (same hazard fixed in ``enforce_rps_uniqueness``). + max_iter = 100 + for _ in range(max_iter): + term = equation.structure[term_idx] for factor in term.structure: - if objective[0] == objective[1].target_idx: + if term_idx == equation.target_idx: continue - # if objective[0] < altered_objective.target_idx: - # corresponding_weight = altered_objective.weights_internal[objective[0]] - # else: - # corresponding_weight = altered_objective.weights_internal[objective[0] - 1] - # if corresponding_weight == 0: parameter_selection = deepcopy(factor.params) for param_idx, param_properties in factor.params_description.items(): if np.random.random() < self.params['r_param_mutation'] and param_properties['name'] not in unmutable_params: @@ -187,21 +197,20 @@ def apply(self, objective : tuple, arguments : dict): # term_idx, objective shift = 0 continue if isinstance(interval[0], int): - shift = np.rint(np.random.normal(loc= 0, scale = self.params['multiplier']*(interval[1] - interval[0]))).astype(int) # + shift = np.rint(np.random.normal(loc=0, scale=self.params['multiplier']*(interval[1] - interval[0]))).astype(int) elif isinstance(interval[0], float): - shift = np.random.normal(loc= 0, scale = self.params['multiplier']*(interval[1] - interval[0])) + shift = np.random.normal(loc=0, scale=self.params['multiplier']*(interval[1] - interval[0])) else: - raise ValueError('In current version of framework only integer and real values for parameters are supported') + raise ValueError('In current version of framework only integer and real values for parameters are supported') if self.params['strict_restrictions']: parameter_selection[param_idx] = np.min((np.max((parameter_selection[param_idx] + shift, interval[0])), interval[1])) else: parameter_selection[param_idx] = parameter_selection[param_idx] + shift factor.params = parameter_selection term.structure = filter_powers(term.structure) - print(f'checking presence of {term.name} as {objective[0]}-th element in {objective[1].text_form}') - # if check_uniqueness(term, objective[1].structure[:objective[0]] + - # objective[1].structure[objective[0]+1:]): - if len(objective[1].terms_labels) == len(objective[1].structure): + equation._invalidate_label_cache() + signatures = {t.factors_labels for t in equation.structure} + if len(signatures) == len(equation.structure): break term.reset_saved_state() return term diff --git a/epde/operators/multiobjective/variation.py b/epde/operators/multiobjective/variation.py index 3ccbf3dd..f8918111 100644 --- a/epde/operators/multiobjective/variation.py +++ b/epde/operators/multiobjective/variation.py @@ -92,11 +92,11 @@ def apply(self, objective : ParetoLevels, arguments : dict): assert len(crossover_pool[pair_idx, 0].vals[eq_key].terms_labels) == len(crossover_pool[pair_idx, 0].vals[eq_key].structure) assert len(crossover_pool[pair_idx, 1].vals[eq_key].terms_labels) == len(crossover_pool[pair_idx, 1].vals[eq_key].structure) - # if len(new_system_1.vars_to_describe) > 1 and np.random.random() < 0.2: - # key = np.random.choice(new_system_1.vars_to_describe) - # temp = deepcopy(new_system_1.vals.chromosome[key]) - # new_system_1.vals.chromosome[key] = new_system_2.vals.chromosome[key] - # new_system_2.vals.chromosome[key] = temp + if len(new_system_1.vars_to_describe) > 1 and np.random.random() < 0.2: + key = np.random.choice(new_system_1.vars_to_describe) + temp = deepcopy(new_system_1.vals.chromosome[key]) + new_system_1.vals.chromosome[key] = new_system_2.vals.chromosome[key] + new_system_2.vals.chromosome[key] = temp offsprings.extend([new_system_1, new_system_2]) @@ -185,24 +185,36 @@ def apply(self, objective : tuple, arguments : dict): equation1.structure = flatten(equation1_terms); equation2.structure = flatten(equation2_terms) - for term in equation1.structure: - if term.term_label not in equation1.terms_labels: + # Inject parent2's "similar but not identical" terms into equation1 + # (and vice versa) when they don't already appear there. The previous + # version iterated ``equation1.structure`` here, but every term in + # that list is already in ``equation1.terms_labels`` by construction, + # so the loop was a no-op. The intent is to take partner-only similar + # terms from the OTHER parent's similar bucket. + eq1_signatures = {t.factors_labels for t in equation1.structure} + for term in equation2_terms[1]: + if term.factors_labels not in eq1_signatures: equation1.structure.append(term) + eq1_signatures.add(term.factors_labels) + eq2_signatures = {t.factors_labels for t in equation2.structure} for term in equation1_terms[1]: - if term.term_label not in equation2.terms_labels: + if term.factors_labels not in eq2_signatures: equation2.structure.append(term) + eq2_signatures.add(term.factors_labels) for i in range(len(equation1.structure)): - if equation1.structure[i].term_label == equation1_target_term.term_label: + if equation1.structure[i].factors_labels == equation1_target_term.factors_labels: equation1.target_idx = i break for i in range(len(equation2.structure)): - if equation2.structure[i].term_label == equation2_target_term.term_label: + if equation2.structure[i].factors_labels == equation2_target_term.factors_labels: equation2.target_idx = i break + equation1._invalidate_label_cache() + equation2._invalidate_label_cache() return equation1, equation2 def use_default_tags(self): @@ -210,11 +222,11 @@ def use_default_tags(self): class EquationExchangeCrossover(CompoundOperator): key = 'EquationExchangeCrossover' - + @HistoryExtender(f'\n -> performing equation exchange crossover', 'ba') def apply(self, objective : tuple, arguments : dict): self_args, subop_args = self.parse_suboperator_args(arguments = arguments) - + # objective[0].structure, objective[1].structure = objective[1].structure, objective[0].structure return objective[0], objective[1] diff --git a/epde/operators/singleobjective/mutations.py b/epde/operators/singleobjective/mutations.py index e8ac9ac1..2bd9d7d3 100644 --- a/epde/operators/singleobjective/mutations.py +++ b/epde/operators/singleobjective/mutations.py @@ -7,6 +7,7 @@ """ import numpy as np +import warnings from copy import deepcopy from functools import partial from typing import Union @@ -60,11 +61,15 @@ class EquationMutation(CompoundOperator): @HistoryExtender(f'\n -> mutating equation', 'ba') def apply(self, objective : Equation, arguments : dict): - self_args, subop_args = self.parse_suboperator_args(arguments = arguments) + self_args, subop_args = self.parse_suboperator_args(arguments = arguments) + mutated = False for term_idx in range(objective.n_immutable, len(objective.structure)): if np.random.uniform(0, 1) <= self.params['r_mutation']: objective.structure[term_idx] = self.suboperators['mutation'].apply(objective = (term_idx, objective), arguments = subop_args['mutation']) + mutated = True + if mutated: + objective._invalidate_label_cache() return objective def use_default_tags(self): @@ -160,23 +165,17 @@ def apply(self, objective : tuple, arguments : dict): # term_idx, objective unmutable_params = {'dim', 'power'} # objective[1] = deepcopy(objective[1]) - while True: - # Костыль! - print('ENTERING LOOP') - try: - objective[1].target_idx - except AttributeError: - objective[1].target_idx = 0 - # - term = objective[1].structure[objective[0]] + try: + objective[1].target_idx + except AttributeError: + objective[1].target_idx = 0 + + max_iter = 100 + for _ in range(max_iter): + term = objective[1].structure[objective[0]] for factor in term.structure: if objective[0] == objective[1].target_idx: continue - # if objective[0] < altered_objective.target_idx: - # corresponding_weight = altered_objective.weights_internal[objective[0]] - # else: - # corresponding_weight = altered_objective.weights_internal[objective[0] - 1] - # if corresponding_weight == 0: parameter_selection = deepcopy(factor.params) for param_idx, param_properties in factor.params_description.items(): if np.random.random() < self.params['r_param_mutation'] and param_properties['name'] not in unmutable_params: @@ -189,17 +188,22 @@ def apply(self, objective : tuple, arguments : dict): # term_idx, objective elif isinstance(interval[0], float): shift = np.random.normal(loc= 0, scale = self.params['multiplier']*(interval[1] - interval[0])) else: - raise ValueError('In current version of framework only integer and real values for parameters are supported') + raise ValueError('In current version of framework only integer and real values for parameters are supported') if self.params['strict_restrictions']: parameter_selection[param_idx] = np.min((np.max((parameter_selection[param_idx] + shift, interval[0])), interval[1])) else: parameter_selection[param_idx] = parameter_selection[param_idx] + shift factor.params = parameter_selection term.structure = filter_powers(term.structure) - print(f'checking presence of {term.name} as {objective[0]}-th element in {objective[1].text_form}') - if check_uniqueness(term, objective[1].structure[:objective[0]] + + objective[1]._invalidate_label_cache() + if check_uniqueness(term, objective[1].structure[:objective[0]] + objective[1].structure[objective[0]+1:]): break + else: + warnings.warn( + f"TermParameterMutation: no unique mutation found in {max_iter} attempts; " + "leaving last candidate (may duplicate an existing term)." + ) term.reset_saved_state() return term diff --git a/epde/operators/singleobjective/variation.py b/epde/operators/singleobjective/variation.py index 1ee5b61f..eb387c86 100644 --- a/epde/operators/singleobjective/variation.py +++ b/epde/operators/singleobjective/variation.py @@ -175,10 +175,12 @@ def apply(self, objective : tuple, arguments : dict): for i in range(same_num + similar_num, len(objective[0].structure)): if check_uniqueness(objective[0].structure[i], objective[1].structure) and check_uniqueness(objective[1].structure[i], objective[0].structure): - objective[0].structure[i], objective[1].structure[i] = self.suboperators['term_crossover'].apply(objective = (objective[0].structure[i], + objective[0].structure[i], objective[1].structure[i] = self.suboperators['term_crossover'].apply(objective = (objective[0].structure[i], objective[1].structure[i]), arguments = subop_args['term_crossover']) - + + objective[0]._invalidate_label_cache() + objective[1]._invalidate_label_cache() return objective[0], objective[1] def use_default_tags(self): @@ -186,12 +188,14 @@ def use_default_tags(self): class EquationExchangeCrossover(CompoundOperator): key = 'EquationExchangeCrossover' - + @HistoryExtender(f'\n -> performing equation exchange crossover', 'ba') def apply(self, objective : tuple, arguments : dict): self_args, subop_args = self.parse_suboperator_args(arguments = arguments) - + objective[0].structure, objective[1].structure = objective[1].structure, objective[0].structure + objective[0]._invalidate_label_cache() + objective[1]._invalidate_label_cache() return objective[0], objective[1] def use_default_tags(self): diff --git a/epde/structure/main_structures.py b/epde/structure/main_structures.py index b530bfa3..761b6fda 100644 --- a/epde/structure/main_structures.py +++ b/epde/structure/main_structures.py @@ -37,6 +37,42 @@ from epde.supplementary import filter_powers, normalize_ts, population_sort, flatten, rts, exp_form, minmax_normalize +_DEFAULT_EQUATION_METAPARAMETERS = { + 'sparsity': {'optimizable': True, 'value': 1.}, + 'terms_number': {'optimizable': False, 'value': 5.}, + 'max_factors_in_term': {'optimizable': False, 'value': 1.}, +} + + +def _deepcopy_slots(src, memo, attrs_to_avoid_copy=()): + """Slot-aware deep copy used by Term/Equation/SoEq. + + Replicates the loop that previously lived in each class's __deepcopy__: + iterate __slots__, skip attrs in attrs_to_avoid_copy (sets them to None + instead), tolerate slots that are not yet set (AttributeError -> skip), + deepcopy lists element-by-element so subclassed list types survive. + + A free function (not a mixin) because __slots__ classes cannot gain a new + attribute via mixin without redeclaring slots; a helper sidesteps that. + """ + clss = src.__class__ + new_struct = clss.__new__(clss) + memo[id(src)] = new_struct + for k in src.__slots__: + try: + if k in attrs_to_avoid_copy: + setattr(new_struct, k, None) + else: + value = getattr(src, k) + if isinstance(value, list): + setattr(new_struct, k, [copy.deepcopy(elem, memo) for elem in value]) + else: + setattr(new_struct, k, copy.deepcopy(value, memo)) + except AttributeError: + pass + return new_struct + + class Term(ComplexStructure): """ Class for describing the term of differential equation @@ -56,8 +92,19 @@ class Term(ComplexStructure): 'pool', 'max_factors_in_term', 'cache_linked', 'occupied_tokens_labels', '_descr_variable_marker'] - def __init__(self, pool, passed_term=None, mandatory_family=None, max_factors_in_term=1, - create_derivs: bool = False, interelement_operator=np.multiply, collapse_powers = True): + def __init__(self, pool: 'TFPool', passed_term=None, mandatory_family: str = None, + max_factors_in_term: Union[int, dict] = 1, + create_derivs: bool = False, interelement_operator: Callable = np.multiply, + collapse_powers: bool = True): + """ + Construct a single Term (a product of Factor objects). + + If ``passed_term`` is None, the term is randomized from ``pool`` honoring + ``max_factors_in_term`` and any ``mandatory_family`` constraint. If + ``passed_term`` is a list/str, the term is built from the supplied factors + and ``collapse_powers`` controls whether identical factors are collapsed + into a single factor with summed power. + """ super().__init__(interelement_operator) self.pool = pool self.max_factors_in_term = max_factors_in_term @@ -207,8 +254,8 @@ def descr_variable_marker(self, marker: False): def evaluate(self, structural, grids=None): assert global_var.tensor_cache is not None, 'Currently working only with connected cache' normalize = structural - if self.saved[structural] or (self.term_label, normalize) in global_var.tensor_cache: - value = global_var.tensor_cache.get(self.term_label, normalized=normalize, + if self.saved[structural] or (self.factors_labels, normalize) in global_var.tensor_cache: + value = global_var.tensor_cache.get(self.factors_labels, normalized=normalize, saved_as=self.saved_as[normalize]) value = value.reshape(-1) return value @@ -228,13 +275,14 @@ def evaluate(self, structural, grids=None): # value *= factor_value_normalized if np.all([len(factor.params) == 1 for factor in self.structure]) and grids is None: # Место возможных проблем: сохранение/загрузка нормализованных данных - self.saved[normalize] = global_var.tensor_cache.add(self.term_label, value, normalized=normalize) + self.saved[normalize] = global_var.tensor_cache.add(self.factors_labels, value, normalized=normalize) if self.saved[normalize]: - self.saved_as[normalize] = self.term_label + self.saved_as[normalize] = self.factors_labels value = value.reshape(-1) return value - def filter_tokens_by_right_part(self, reference_target, equation, equation_position): + def filter_tokens_by_right_part(self, reference_target, equation, equation_position, + max_retries: int = 100): warnings.warn(message='Tokens can no longer be set as right-part-unique', category=DeprecationWarning) taken_tokens = [factor.label for factor in reference_target.structure @@ -242,9 +290,8 @@ def filter_tokens_by_right_part(self, reference_target, equation, equation_posit meaningful_taken = any([factor.status['meaningful'] for factor in reference_target.structure if factor.status['unique_for_right_part']]) - accept_term_try = 0 - while True: - accept_term_try += 1 + new_term = None + for accept_term_try in range(1, max_retries + 1): new_term = copy.deepcopy(self) for factor_idx, factor in enumerate(new_term.structure): if factor.label in taken_tokens: @@ -255,14 +302,17 @@ def filter_tokens_by_right_part(self, reference_target, equation, equation_posit self.structure = new_term.structure self.structure = filter_powers(self.structure) self.reset_saved_state() - break + return if accept_term_try == 10 and global_var.verbose.show_warnings: warnings.warn('Can not create unique term, while filtering equation tokens in regards to the right part.') if accept_term_try >= 10: self.randomize(forbidden_factors=new_term.occupied_tokens_labels + taken_tokens) - if accept_term_try == 100: - print('Something wrong with the random generation of term while running "filter_tokens_by_right_part"') - print('proposed', new_term.name, 'for ', equation.text_form, 'with respect to', reference_target.name) + + last_attempt_name = new_term.name if new_term is not None else '' + raise RuntimeError( + f'filter_tokens_by_right_part: failed to create unique term after ' + f'{max_retries} retries. Last attempted: {last_attempt_name} for ' + f'{equation.text_form} with respect to {reference_target.name}') def reset_occupied_tokens(self): occupied_tokens_new = [] @@ -286,6 +336,20 @@ def available_tokens(self): available_tokens.append(token_new) return available_tokens + def iter_available_tokens(self): + """Generator equivalent of `available_tokens`; yields one filtered family at a time. + + Allows consumers that only need to iterate (rather than realize the full + list) to avoid the per-call list materialization. Each yielded family is + still deepcopied — that's the unavoidable per-element cost. + """ + for token in self.pool.families: + if not all([label in self.occupied_tokens_labels for label in token.tokens]): + token_new = copy.deepcopy(token) + token_new.tokens = [ + label for label in token.tokens if label not in self.occupied_tokens_labels] + yield token_new + @property def total_params(self): return max(sum([len(element.params) - 1 for element in self.structure]), 1) @@ -331,31 +395,16 @@ def __eq__(self, other): @HistoryExtender('\n -> was copied by deepcopy(self)', 'n') def __deepcopy__(self, memo=None): - clss = self.__class__ - new_struct = clss.__new__(clss) - memo[id(self)] = new_struct - - attrs_to_avoid_copy = [] - for k in self.__slots__: - try: - if k not in attrs_to_avoid_copy: - if not isinstance(k, list): - setattr(new_struct, k, copy.deepcopy( - getattr(self, k), memo)) - else: - temp = [] - for elem in getattr(self, k): - temp.append(copy.deepcopy(elem, memo)) - setattr(new_struct, k, temp) - else: - setattr(new_struct, k, None) - except AttributeError: - pass - - return new_struct + return _deepcopy_slots(self, memo) @property - def term_label_without_power(self): + def factors_labels_without_power(self) -> frozenset: + """Return a frozenset of factor labels with the power parameter dropped. + + Each entry is either the cache label tuple or its head plus the trailing + param (when the factor has more than one parameter). Used to compare + terms for structural identity ignoring power differences. + """ described = set() for factor in self.structure: if len(factor.params) == 1: @@ -367,7 +416,13 @@ def term_label_without_power(self): return described @property - def term_label(self): + def factors_labels(self) -> frozenset: + """Return a frozenset of canonical labels for each factor in the term. + + Trigonometric factors collapse the ``freq`` parameter (kept fungible + across small frequency ranges); other factors use ``factor.cache_label`` + directly. Used as a hashable identity for set/membership checks. + """ described = set() for factor in self.structure: if factor.ftype == 'trigonometric': @@ -378,6 +433,16 @@ def term_label(self): described = frozenset(described) return described + @property + def term_label_without_power(self): + # TODO(deprecate): use factors_labels_without_power + return self.factors_labels_without_power + + @property + def term_label(self): + # TODO(deprecate): use factors_labels + return self.factors_labels + class Equation(ComplexStructure): __slots__ = ['_history', 'structure', 'interelement_operator', 'n_immutable', 'pool', @@ -385,13 +450,12 @@ class Equation(ComplexStructure): 'target_idx', 'right_part_selected', '_weights_final', 'weights_final_evald', 'simplified', 'is_correct_right_part', '_weights_internal', 'weights_internal_evald', 'fitness_calculated', 'stability_calculated', 'aic_calculated', 'solver_form_defined', '_fitness_value', '_coefficients_stability', '_aic', 'metaparameters', 'main_var_to_explain', - '_eval_cache', '_cached_sw_weights'] # , '_solver_form' + '_eval_cache', '_cached_sw_weights', + '_terms_labels_cache', '_terms_labels_without_power_cache'] # , '_solver_form' def __init__(self, pool: TFPool, basic_structure: Union[list, tuple, set], var_to_explain: str = None, - metaparameters: dict = {'sparsity': {'optimizable': True, 'value': 1.}, - 'terms_number': {'optimizable': False, 'value': 5.}, - 'max_factors_in_term': {'optimizable': False, 'value': 1.}}, + metaparameters: dict = None, interelement_operator: Callable = np.add): """ @@ -430,6 +494,9 @@ def __init__(self, pool: TFPool, basic_structure: Union[list, tuple, set], var_t super().__init__(interelement_operator) self.reset_state() + if metaparameters is None: + metaparameters = copy.deepcopy(_DEFAULT_EQUATION_METAPARAMETERS) + self.n_immutable = len(basic_structure) self.pool = pool self.structure = [] @@ -448,21 +515,27 @@ def __init__(self, pool: TFPool, basic_structure: Union[list, tuple, set], var_t self.main_var_to_explain = var_to_explain force_var_to_explain = True # False + max_iter = 100 for i in range(len(basic_structure), int(self.metaparameters['terms_number']['value'])): new_term = Term(self.pool, max_factors_in_term=self.metaparameters['max_factors_in_term']['value'], mandatory_family=None, passed_term=None) - while new_term.term_label in self.terms_labels: + for _ in range(max_iter): + if new_term.factors_labels not in self.terms_labels: + break new_term.randomize() new_term.reset_saved_state() - # check_test += 1 - # - - - # if new_term.described_variables_extra not in self.described_variables_full: - # force_var_to_explain = False - # break - + else: + # Pool can't yield a unique term against the current + # structure -- stop, don't try further slots. Subsequent + # ``new_term`` draws would face the same exhausted pool, + # so the only honest outcome is a shorter equation. + warnings.warn( + f"Equation.__init__: no unique term in {max_iter} attempts at slot {i}; " + "pool may be exhausted -- stopping with a shorter equation." + ) + break self.structure.append(new_term) + self._invalidate_label_cache() for idx, _ in enumerate(self.structure): self.structure[idx].use_cache() @@ -487,6 +560,7 @@ def manual_reconst(self, attribute:str, value, except_attrs:dict): attrs_from_dict(term, term_elem, except_attrs) self.structure.append(term) + self._invalidate_label_cache() def reset_explaining_term(self, term_idx=0): for idx, term in enumerate(self.structure): @@ -509,8 +583,17 @@ def remove_zero_terms(self): if self.weights_internal[idx] == 0: target_bias += 1 if i < self.target_idx else 0 zero_terms.append(i) - self.structure = [term for term_idx, term in enumerate(self.structure) if term_idx not in zero_terms] - self.target_idx -= target_bias + if zero_terms: + self.structure = [term for term_idx, term in enumerate(self.structure) if term_idx not in zero_terms] + self.target_idx -= target_bias + # ``_invalidate_label_cache`` also wipes _eval_cache, which + # is essential here: the right-part-selector's per-target + # sweep populates the cache keyed on target_idx, and the + # adjusted target_idx above can collide with a swept value. + # ``_cached_sw_weights`` was computed for the surviving + # features and still aligns with the new structure, so it + # is preserved. + self._invalidate_label_cache() def __eq__(self, other): @@ -548,27 +631,58 @@ def restore_property(self, deriv: bool = False, mandatory_family: bool = False, # TODO: non-urgent, rewrite for an arbitrary equation property check if not (deriv or mandatory_family): raise ValueError('No property passed for restoration.') - while True: - # print( - # f'Restoring containment of {mandatory_family} in {self.text_form}.') + # Bound both the outer and the inner sampling loops, and reject any + # candidate whose factor signature would collide with another + # existing term -- see feedback-structure-dedup memory. + max_outer = 200 + max_inner = 100 + + def _would_duplicate(idx, candidate): + sig = candidate.factors_labels + return any(j != idx and other.factors_labels == sig + for j, other in enumerate(self.structure)) + + mf_marker = self.main_var_to_explain if mandatory_family else None + max_factors = self.metaparameters['max_factors_in_term']['value'] + for _ in range(max_outer): replacement_idx = np.random.randint(low=0, high=len(self.structure)) - mf_marker = self.main_var_to_explain if mandatory_family else None - temp = Term(self.pool, mandatory_family=mf_marker, - max_factors_in_term=self.metaparameters['max_factors_in_term']['value']) + temp = Term(self.pool, mandatory_family=mf_marker, max_factors_in_term=max_factors) if t_derivative: - while not temp.contains_t_derivative(): - temp = Term(self.pool, mandatory_family=mf_marker, - max_factors_in_term=self.metaparameters['max_factors_in_term']['value']) - break + inner = 0 + while not temp.contains_t_derivative() and inner < max_inner: + temp = Term(self.pool, mandatory_family=mf_marker, max_factors_in_term=max_factors) + inner += 1 + if not temp.contains_t_derivative(): + continue + if _would_duplicate(replacement_idx, temp): + continue + self.structure[replacement_idx] = temp + self._invalidate_label_cache() + return if deriv and mandatory_family and temp.contains_deriv() and temp.contains_variable(self.main_var_to_explain): + if _would_duplicate(replacement_idx, temp): + continue self.structure[replacement_idx] = temp - break + self._invalidate_label_cache() + return elif deriv and temp.contains_deriv(self.main_var_to_explain) and not mandatory_family: + if _would_duplicate(replacement_idx, temp): + continue self.structure[replacement_idx] = temp - break + self._invalidate_label_cache() + return elif mandatory_family and temp.contains_variable(self.main_var_to_explain) and not deriv: + if _would_duplicate(replacement_idx, temp): + continue self.structure[replacement_idx] = temp - break + self._invalidate_label_cache() + return + warnings.warn( + f'Equation.restore_property: could not satisfy ' + f'deriv={deriv}, mandatory_family={mandatory_family}, ' + f't_derivative={t_derivative} without duplication after ' + f'{max_outer} attempts; leaving structure unchanged.' + ) def reconstruct_by_right_part(self, right_part_idx): warnings.warn(message='Tokens can no longer be set as right-part-unique', @@ -585,9 +699,29 @@ def reconstruct_by_right_part(self, right_part_idx): new_eq.reset_saved_state() return new_eq - def evaluate(self, normalize=True, return_val=False, grids=None): - cache_key = (normalize, return_val, grids is None) - if grids is None and hasattr(self, '_eval_cache') and cache_key in self._eval_cache: + def evaluate(self, normalize: bool = True, return_val: bool = False, + grids: list = None) -> Tuple: + """Evaluate the equation and return (value, target, features). + + ``target`` is the LHS term values; ``features`` is a 2-D matrix of the + non-target term evaluations (``None`` if every other term is zero-weight + and ``normalize=False``); ``value`` is the residual when + ``return_val=True`` else ``None``. + + Caching policy: results are cached per + (normalize, return_val, grids-is-None, target_idx) when + ``grids is None`` AND ``normalize`` is True. The ``normalize=False`` + branch additionally filters ``feature_indexes`` by the current + ``weights_internal`` (lines below); since callers update weights + between successive ``evaluate(normalize=False)`` calls, caching that + branch would risk returning stale (target, features) tuples with + out-of-date feature masks. ``normalize=True`` is weight-independent + and is the path benefitting from cache hits (sparsity then L2LRFitness + both call ``evaluate(normalize=True)`` in one fitness invocation). + """ + cacheable = (grids is None) and normalize + cache_key = (normalize, return_val, grids is None, self.target_idx) + if cacheable and hasattr(self, '_eval_cache') and cache_key in self._eval_cache: return self._eval_cache[cache_key] target = self.structure[self.target_idx].evaluate(False, grids=grids) @@ -629,18 +763,24 @@ def evaluate(self, normalize=True, return_val=False, grids=None): else: features_val = np.zeros_like(target) value = np.add(elem1, - features_val) - # print(value.shape) result = (value, target, features) else: result = (None, target, features) - if grids is None: + if cacheable: if not hasattr(self, '_eval_cache'): self._eval_cache = {} self._eval_cache[cache_key] = result return result - def reset_state(self, reset_right_part: bool = True): + def reset_state(self, reset_right_part: bool = True) -> None: + """Drop all cached evaluation/fitness state on this Equation. + + Call after any structural mutation (or to discard a stale fitness/AIC + evaluation). Set ``reset_right_part=False`` to keep target_idx and + weight assignments — useful when only the LHS-derived caches need + clearing. + """ if reset_right_part: self.right_part_selected = False self.is_correct_right_part = False @@ -660,32 +800,33 @@ def reset_state(self, reset_right_part: bool = True): self.aic_calculated = False self.solver_form_defined = False self._eval_cache = {} + # consumed by epde.operators.common.fitness.L2LRFitness; resets here. self._cached_sw_weights = None + self._terms_labels_cache = None + self._terms_labels_without_power_cache = None + + def _invalidate_label_cache(self): + """Drop memoized caches keyed on the current structure; call after + ``self.structure`` (or ``self.target_idx``) mutates. + + Covers both the terms-labels caches and the per-evaluation + ``_eval_cache`` populated by :meth:`evaluate`. The eval cache key + includes ``self.target_idx`` and the cached value depends on which + terms occupy ``self.structure``, so any structural mutation must + drop it -- otherwise callers like the right-part-selector's + per-target sweep can leave stale entries that survive into the + post-RPS fitness call (e.g. after ``remove_zero_terms`` adjusts + ``target_idx`` onto a value the sweep already cached). + """ + self._terms_labels_cache = None + self._terms_labels_without_power_cache = None + if hasattr(self, '_eval_cache'): + self._eval_cache = {} @HistoryExtender('\n -> was copied by deepcopy(self)', 'n') def __deepcopy__(self, memo=None): - clss = self.__class__ - new_struct = clss.__new__(clss) - memo[id(self)] = new_struct - - attrs_to_avoid_copy = [] - for k in self.__slots__: - try: - if k not in attrs_to_avoid_copy: - if not isinstance(k, list): - setattr(new_struct, k, copy.deepcopy(getattr(self, k), memo)) - else: - temp = [] - for elem in getattr(self, k): - temp.append(copy.deepcopy(elem, memo)) - setattr(new_struct, k, temp) - else: - setattr(new_struct, k, None) - except AttributeError: - pass - - return new_struct + return _deepcopy_slots(self, memo) def copy_properties_to(self, new_equation): new_equation.weights_internal_evald = self.weights_internal_evald @@ -714,20 +855,34 @@ def copy_properties_to(self, new_equation): pass def add_history(self, add): - # print(add) self._history += add - def add_random_term(self): + def add_random_term(self) -> bool: + """Try to append one fresh, non-duplicate term to ``self.structure``. + + Returns ``True`` if a term was appended, ``False`` if either the + ``terms_number`` cap was already reached or the token pool could + not produce a non-duplicate within ``max_iter`` retries. Callers + that invoke this in a loop (e.g. ``EquationMutation.apply``, + ``Equation.__init__``) MUST stop on the first ``False`` -- once + the pool stops yielding uniques, further calls will not yield any + either, and continuing past the failure pushes downstream + operators (``_scrub_conflicting_terms``, ``EqRightPartSelector``) + into states that violate the duplicate-term invariant. + """ + cap = int(self.metaparameters['terms_number']['value']) + if len(self.structure) >= cap: + return False + max_iter = 10 new_term = Term(self.pool, max_factors_in_term=self.metaparameters['max_factors_in_term']['value'], mandatory_family=None, passed_term=None) - - attempt = 0 - while new_term.term_label in self.terms_labels or attempt < 10: + for _ in range(max_iter): + if new_term.factors_labels not in self.terms_labels: + self.structure.append(deepcopy(new_term)) + self._invalidate_label_cache() + return True new_term.randomize() - attempt += 1 - - if attempt < 10: - self.structure.append(deepcopy(new_term)) + return False @property def history(self): @@ -803,7 +958,7 @@ def text_form(self): form += 'k_' + str(term_idx) + ' ' + \ self.structure[term_idx].name + ' + ' form += 'k_' + str(len(self.structure)) + ' = 0' - except: + except (AttributeError, IndexError, TypeError): form = '' return form @@ -831,7 +986,19 @@ def state(self): return self.text_form @property - def terms_labels_without_power(self): + def terms_labels_without_power(self) -> frozenset: + """Frozenset of per-term factor-label sets, with the power parameter dropped. + + Skips terms whose internal weight is exactly zero (target term always + contributes). Memoized in ``_terms_labels_without_power_cache``; the + 15 call sites of :meth:`_invalidate_label_cache` cover every + Equation-driven structure mutation. Term-level mutations from + external operators that bypass the Equation must invalidate the + cache themselves. + """ + cached = getattr(self, '_terms_labels_without_power_cache', None) + if cached is not None: + return cached described = set() for term_idx, term in enumerate(self.structure): cache_label = set() @@ -852,13 +1019,23 @@ def terms_labels_without_power(self): factor_label = (factor.cache_label[0], (factor.cache_label[1][-1])) cache_label.add(factor_label) if len(cache_label) > 0: - cache_label = frozenset(cache_label) - described.add(cache_label) - described = frozenset(described) - return described + described.add(frozenset(cache_label)) + result = frozenset(described) + self._terms_labels_without_power_cache = result + return result @property - def terms_labels(self): + def terms_labels(self) -> frozenset: + """Frozenset of per-term factor-label sets identifying this equation's structure. + + Each inner element is the ``Term.factors_labels`` of one term. Used as + a hashable structural fingerprint for membership tests against + ``objective.history``. Memoized in ``_terms_labels_cache``; see + ``terms_labels_without_power`` for invalidation contract. + """ + cached = getattr(self, '_terms_labels_cache', None) + if cached is not None: + return cached described = set() for term_idx, term in enumerate(self.structure): cache_label = set() @@ -868,10 +1045,20 @@ def terms_labels(self): cache_label.add(label) else: cache_label.add(factor.cache_label) - cache_label = frozenset(cache_label) - described.add(cache_label) - described = frozenset(described) - return described + described.add(frozenset(cache_label)) + result = frozenset(described) + self._terms_labels_cache = result + return result + + @property + def factors_labels(self) -> frozenset: + """Alias of ``terms_labels`` — naming mirror used by some operators.""" + return self.terms_labels + + @property + def factors_labels_without_power(self) -> frozenset: + """Alias of ``terms_labels_without_power``.""" + return self.terms_labels_without_power def max_deriv_orders(self): solver_form = self.solver_form() @@ -994,8 +1181,9 @@ def check_metaparameters(metaparameters: dict): class SoEq(moeadd.MOEADDSolution): - def __init__(self, pool: TFPool, metaparameters: dict): + def __init__(self, pool: TFPool, metaparameters: dict) -> None: ''' + Top-level solution gene: a system of one Equation per variable. Parameters ---------- @@ -1045,13 +1233,12 @@ def use_default_multiobjective_function(self, use_pic: bool = False): self.use_legacy_multiobjective_function() def use_legacy_multiobjective_function(self): - from epde.eq_mo_objectives import generate_partial, equation_fitness, equation_complexity_by_factors - complexity_objectives = [generate_partial(equation_complexity_by_factors, eq_key) - for eq_key in self.vars_to_describe] - quality_objectives = [generate_partial( - equation_fitness, eq_key) for eq_key in self.vars_to_describe] - self.set_objective_functions( - quality_objectives + complexity_objectives) + from epde.eq_mo_objectives import equation_fitness, equation_complexity_by_factors + # Both functions return per-equation tuples when called without an + # equation_key, so the overall obj_fun layout matches the NEW path + # (one weight per objective TYPE, expanded across equations by + # MOEA/D). See penalty_based_intersection for the expansion logic. + self.set_objective_functions([equation_fitness, equation_complexity_by_factors]) def use_pic_multiobjective_function(self): from epde.eq_mo_objectives import generate_partial, equation_fitness, equation_complexity_by_factors, equation_terms_stability, equation_aic @@ -1192,27 +1379,15 @@ def __hash__(self): return hash(self.vals.hash_descr) def __deepcopy__(self, memo=None): - clss = self.__class__ - new_struct = clss.__new__(clss) - memo[id(self)] = new_struct - + # SoEq has no own __slots__; the helper iterates the inherited + # (likely empty) ABC slots harmlessly. Then carry the __dict__ over. + new_struct = _deepcopy_slots(self, memo) for k, v in self.__dict__.items(): setattr(new_struct, k, copy.deepcopy(v, memo)) - - for k in self.__slots__: - try: - if not isinstance(k, list): - setattr(new_struct, k, copy.deepcopy(getattr(self, k), memo)) - else: - temp = [] - for elem in getattr(self, k): - temp.append(copy.deepcopy(elem, memo)) - setattr(new_struct, k, temp) - except AttributeError: - pass return new_struct - def reset_state(self, reset_right_part: bool = True): + def reset_state(self, reset_right_part: bool = True) -> None: + """Forward reset_state to every Equation in this system.""" for equation in self.vals: equation.reset_state(reset_right_part) @@ -1220,9 +1395,13 @@ def copy_properties_to(self, objective): for eq_label in self.vals.equation_keys: # Not the best code possible here self.vals[eq_label].copy_properties_to(objective.vals[eq_label]) - def solver_params(self, full_domain, grids=None): + def solver_params(self, full_domain: bool, grids: list = None) -> Tuple: ''' - Returns solver form, grid and boundary conditions + Return solver form, grid and boundary conditions for every equation. + + Pass ``full_domain=True`` to read from the initial-data cache (the + complete sampled domain) instead of the active grid cache. ``grids`` + overrides the implicit grid used to evaluate solver forms. ''' equation_forms = [] bconds = [] @@ -1242,19 +1421,39 @@ def fitness_calculated(self): return all([equation.fitness_calculated for equation in self.vals]) @property - def terms_labels_without_power(self): + def equations_labels_without_power(self) -> Tuple[frozenset, ...]: + """Tuple of ``Equation.terms_labels_without_power`` for each equation. + + Order matches ``self.vars_to_describe``. Useful for structural identity + checks on the system as a whole (e.g., dedup against history). + """ equations_caches = [] for equation in self.vals: equations_caches.append(equation.terms_labels_without_power) return tuple(equations_caches) @property - def terms_labels(self): + def equations_labels(self) -> Tuple[frozenset, ...]: + """Tuple of ``Equation.terms_labels`` for each equation in the system. + + Element order matches ``self.vars_to_describe``. The hashable per-equation + frozensets enable ``system in objective.history`` membership checks. + """ equations_caches = [] for equation in self.vals: equations_caches.append(equation.terms_labels) return tuple(equations_caches) + @property + def terms_labels_without_power(self): + # TODO(deprecate): use equations_labels_without_power + return self.equations_labels_without_power + + @property + def terms_labels(self): + # TODO(deprecate): use equations_labels + return self.equations_labels + class SoEqIterator(object): def __init__(self, system: SoEq): diff --git a/tests/unit/test_main_structures_characterization.py b/tests/unit/test_main_structures_characterization.py new file mode 100644 index 00000000..4d9b4c34 --- /dev/null +++ b/tests/unit/test_main_structures_characterization.py @@ -0,0 +1,333 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Characterization tests for ``epde/structure/main_structures.py``. + +These tests pin CURRENT behavior (correct or buggy) so the upcoming refactoring +phases can detect regressions. See ``PLAN_main_structures_refinement.md`` for +the staged roadmap they support. + +Some tests pin observed bugs (most prominently the mutable default +metaparameters in ``Equation.__init__`` at l.391-395). Phase 2 fixes those +bugs; the relevant test expectations will flip in the same commit that lands +each fix. +""" + +import copy +from collections import OrderedDict + +import numpy as np +import pytest + +import epde.globals as global_var +from epde.cache.cache import upload_grids, upload_simple_tokens +from epde.evaluators import simple_function_evaluator +from epde.interface.equation_translator import translate_equation +from epde.interface.token_family import TFPool, TokenFamily +from epde.structure.main_structures import Equation + + +# --------------------------------------------------------------------------- +# Fixtures +# --------------------------------------------------------------------------- + +@pytest.fixture(scope="module") +def basic_pool(): + """A minimal pool with two derivative-family tokens (``u`` and ``du/dx0``). + + Avoids ANN training and heavy preprocessing — we only need a pool whose + factors have valid cache labels, evaluator linkage, and pool back-references + so that ``Term``/``Equation``/``SoEq`` construction and deepcopy succeed. + """ + grid = np.linspace(0.0, 4.0 * np.pi, 50) + u = np.sin(grid) + du = np.cos(grid) + + global_var.init_caches(set_grids=True) + global_var.set_time_axis(0) + global_var.init_verbose(show_warnings=False) + global_var.tensor_cache.memory_usage_properties( + obj_test_case=u, mem_for_cache_frac=5) + global_var.grid_cache.memory_usage_properties( + obj_test_case=grid, mem_for_cache_frac=5) + + upload_grids([grid], global_var.grid_cache) + + deriv_names = ['u', 'du/dx0'] + deriv_orders = [[None,], [0,]] + deriv_tensors = np.stack([u, du], axis=0) + upload_simple_tokens(deriv_names, global_var.tensor_cache, deriv_tensors) + global_var.tensor_cache.use_structural() + + u_family = TokenFamily('u', variable='u', family_of_derivs=True) + u_family.set_status(demands_equation=True, unique_specific_token=False, + unique_token_type=False, s_and_d_merged=False, + meaningful=True) + u_family.set_params(deriv_names, OrderedDict([('power', (1, 1))]), + {'power': 0}, deriv_orders) + u_family.set_evaluator(simple_function_evaluator) + + return TFPool([u_family]) + + +def _build_soeq(pool): + text = '1.0 * u{power: 1} + 0.0 = du/dx0{power: 1}' + soeq = translate_equation(text, pool, all_vars=['u']) + # translate_equation assigns weights via the setter but does not flip + # weights_internal_evald. Set it so terms_labels_without_power can run + # without raising AttributeError ("Internal weights called before init"). + eq = soeq.vals['u'] + eq.weights_internal_evald = True + return soeq + + +@pytest.fixture +def soeq(basic_pool): + return _build_soeq(basic_pool) + + +@pytest.fixture +def equation(soeq): + return soeq.vals['u'] + + +@pytest.fixture +def term(equation): + return equation.structure[0] + + +# --------------------------------------------------------------------------- +# 1. TestTermDeepcopy +# --------------------------------------------------------------------------- + +class TestTermDeepcopy: + def test_returns_distinct_object(self, term): + copy_t = copy.deepcopy(term) + assert id(copy_t) != id(term) + + def test_equal_to_original(self, term): + copy_t = copy.deepcopy(term) + assert copy_t == term + + def test_structure_is_fresh(self, term): + copy_t = copy.deepcopy(term) + assert copy_t.structure is not term.structure + for c_factor, o_factor in zip(copy_t.structure, term.structure): + assert c_factor is not o_factor + + def test_preserves_name(self, term): + copy_t = copy.deepcopy(term) + assert copy_t.name == term.name + + def test_preserves_cache_label(self, term): + copy_t = copy.deepcopy(term) + assert copy_t.cache_label == term.cache_label + + +# --------------------------------------------------------------------------- +# 2. TestEquationDeepcopy +# --------------------------------------------------------------------------- + +class TestEquationDeepcopy: + def test_returns_distinct_object(self, equation): + copy_e = copy.deepcopy(equation) + assert id(copy_e) != id(equation) + + def test_equal_to_original(self, equation): + copy_e = copy.deepcopy(equation) + assert copy_e == equation + + def test_structure_is_fresh(self, equation): + copy_e = copy.deepcopy(equation) + assert copy_e.structure is not equation.structure + for c_term, o_term in zip(copy_e.structure, equation.structure): + assert c_term is not o_term + + def test_eval_cache_after_deepcopy_is_fresh_dict(self, equation): + """Pin: __deepcopy__ traverses the _eval_cache slot, so the copy + owns its own dict (initially empty, equal to source's empty dict). + """ + copy_e = copy.deepcopy(equation) + assert copy_e._eval_cache is not equation._eval_cache + assert copy_e._eval_cache == equation._eval_cache + + +# --------------------------------------------------------------------------- +# 3. TestSoEqDeepcopy +# --------------------------------------------------------------------------- + +class TestSoEqDeepcopy: + def test_returns_distinct_object(self, soeq): + copy_s = copy.deepcopy(soeq) + assert id(copy_s) != id(soeq) + + def test_dict_attrs_present(self, soeq): + """Pin current dual-traversal: __dict__ keys are all carried over.""" + copy_s = copy.deepcopy(soeq) + for key in soeq.__dict__: + assert hasattr(copy_s, key) + + def test_vals_independent(self, soeq): + """The chromosome is itself deepcopied, not aliased.""" + copy_s = copy.deepcopy(soeq) + assert copy_s.vals is not soeq.vals + + +# --------------------------------------------------------------------------- +# 4. TestEquationLabelProperties +# --------------------------------------------------------------------------- + +class TestEquationLabelProperties: + def test_terms_labels_is_frozenset_of_frozensets(self, equation): + labels = equation.terms_labels + assert isinstance(labels, frozenset) + for inner in labels: + assert isinstance(inner, frozenset) + + def test_terms_labels_count_matches_unique_terms(self, equation): + # Two distinct terms (u and du/dx0) → two frozenset entries. + assert len(equation.terms_labels) == len(equation.structure) + + def test_terms_labels_without_power_is_frozenset(self, equation): + labels = equation.terms_labels_without_power + assert isinstance(labels, frozenset) + + def test_terms_labels_stable_across_calls(self, equation): + # Calling twice in a row returns equal results (no hidden state). + first = equation.terms_labels + second = equation.terms_labels + assert first == second + + +# --------------------------------------------------------------------------- +# 6. TestRenameAliases (Phase 3) +# +# Pin the alias contract: deprecated old names delegate to new names with +# identical results. If a future commit drops an alias, this test catches it. +# --------------------------------------------------------------------------- + +class TestRenameAliases: + def test_term_alias_factors_labels(self, term): + assert term.factors_labels == term.term_label + + def test_term_alias_factors_labels_without_power(self, term): + assert term.factors_labels_without_power == term.term_label_without_power + + def test_soeq_alias_equations_labels(self, soeq): + assert soeq.equations_labels == soeq.terms_labels + + def test_soeq_alias_equations_labels_without_power(self, soeq): + assert soeq.equations_labels_without_power == soeq.terms_labels_without_power + + +# --------------------------------------------------------------------------- +# 7. TestEquationLabelsAfterTermMutation +# +# terms_labels / terms_labels_without_power are memoized in slot caches +# (_terms_labels_cache, _terms_labels_without_power_cache). Mutation paths +# that touch self.structure or its terms must call _invalidate_label_cache() +# afterward (15 known call sites cover this). These tests pin the new +# contract: fresh result on first access populates the cache, repeated +# access returns the same frozenset, and invalidation drops the cache. +# --------------------------------------------------------------------------- + +class TestEquationLabelsAfterTermMutation: + def test_terms_labels_reflect_structure_append(self, equation): + before = equation.terms_labels + equation.structure.append(copy.deepcopy(equation.structure[0])) + # Manual structure append bypasses Equation's mutation API and the + # cache; an explicit invalidation is the contract for callers that + # touch self.structure directly. + equation._invalidate_label_cache() + after = equation.terms_labels + # frozenset of frozensets — appending a duplicate keeps the frozenset + # the same size (set semantics) but len(structure) grows. + assert len(after) <= len(before) + 1 + assert len(after) <= len(equation.structure) + + def test_terms_labels_populates_cache(self, equation): + # First access computes and stores; subsequent accesses return the + # identical frozenset (cache hit, not a recomputation). + assert equation._terms_labels_cache is None + first = equation.terms_labels + assert equation._terms_labels_cache is first + second = equation.terms_labels + assert second is first + + def test_invalidate_helper_drops_cache(self, equation): + # Calling the helper on a populated equation drops both caches, so + # the next read recomputes from the current structure. + _ = equation.terms_labels + _ = equation.terms_labels_without_power + assert equation._terms_labels_cache is not None + assert equation._terms_labels_without_power_cache is not None + equation._invalidate_label_cache() + assert equation._terms_labels_cache is None + assert equation._terms_labels_without_power_cache is None + + def test_factors_labels_alias_on_equation(self, equation): + # Phase 3 added factors_labels on Term; mutations.py:127 also reads + # it on Equation (treating the names as interchangeable). Pin the alias. + assert equation.factors_labels == equation.terms_labels + assert equation.factors_labels_without_power == equation.terms_labels_without_power + + +# --------------------------------------------------------------------------- +# 8. TestFilterTokensByRightPartExhaustion (Phase 6) +# +# Pin: filter_tokens_by_right_part raises RuntimeError when it cannot find +# a unique term within the retry budget. Pre-Phase-6 the function looped +# forever (or warned and continued); Phase 6 caps retries with a hard fail. +# --------------------------------------------------------------------------- + +class TestFilterTokensByRightPartExhaustion: + def test_raises_runtimeerror_on_exhaustion(self, equation): + import warnings as _w + + # The deprecated function reads factor.status['unique_for_right_part']; + # patch it onto our test factors (the fixture uses the modern token- + # family schema where this key is absent). + for t in equation.structure: + for f in t.structure: + f.status['unique_for_right_part'] = False + + # Force a duplicate so terms_labels never matches len(structure) + # — guaranteeing the loop never breaks out via success. + equation.structure.append(copy.deepcopy(equation.structure[0])) + equation._invalidate_label_cache() + + target = equation.structure[equation.target_idx] + candidate = equation.structure[0] + + with _w.catch_warnings(): + _w.simplefilter('ignore', DeprecationWarning) + with pytest.raises(RuntimeError, match='filter_tokens_by_right_part'): + candidate.filter_tokens_by_right_part( + target, equation, equation_position=0, max_retries=1) + + +# --------------------------------------------------------------------------- +# 5. TestEquationDefaultMetaparameters +# +# After Phase 2: each Equation gets its OWN deep-copied default metaparameters +# dict, so mutating one cannot leak into another. Pre-Phase-2 this test +# asserted the opposite (shared mutation). The flip is the visible artifact +# that the bug at the old l.391-395 has been fixed. +# --------------------------------------------------------------------------- + +class TestEquationDefaultMetaparameters: + def test_two_equations_have_independent_default_metaparameters(self, basic_pool): + # Default terms_number is 5; passing five basic terms skips the + # random-padding loop entirely (range(5, 5) is empty). + eq1 = Equation(basic_pool, basic_structure=['u'] * 5, + var_to_explain='u') + eq2 = Equation(basic_pool, basic_structure=['u'] * 5, + var_to_explain='u') + # Each sees the documented default value. + assert eq1.metaparameters['sparsity']['value'] == 1.0 + assert eq2.metaparameters['sparsity']['value'] == 1.0 + + eq1.metaparameters['sparsity']['value'] = 999.0 + # Mutation MUST stay local — the dict objects are independent. + assert eq2.metaparameters['sparsity']['value'] == 1.0 + assert eq1.metaparameters is not eq2.metaparameters From 20fbf666efbb68c5316b0ec88bb009c0aee51d18 Mon Sep 17 00:00:00 2001 From: Gromwud Date: Wed, 20 May 2026 12:14:01 +0300 Subject: [PATCH 4/8] RPS bidirectional uniqueness + fitness/sparsity/optimization refactor Cumulative work in progress on the EPDE core engine, grouped together because the diffs are interleaved across these files: * SoEqRightPartSelector gains a bidirectional convergence pass: forward sequential RPS pre-scrub + a second pass that re-scrubs each equation against the others' already-selected RPS, fixing the LV-style leak where eq for u kept dv/dx0 as a non-target term. _scrub_conflicting_terms and EqRightPartSelector get bounded loops + duplicate-term assert at the entry point. * L2LRFitness (WAPE) and VWSRSparsity (PhysicsInformedLasso, CV- weighted) wired in as the NEW pipeline; GramSetup precomputed once outside RFE outer loop. * OffspringUpdater in moeadd_specific cleaned up; mutation / offspring attempt counters consolidated. * MOEADD population constructor, strategy, single-criterion strategy: threading of fitness_cls / sparsity_cls / use_pic through the EpdeSearch -> MOEADDDirector.use_baseline path so the three NEW-pipeline axes are independently selectable. * supplementary: GramSetup + sliding-window weight helpers consumed by L2LRFitness. --- epde/eq_mo_objectives.py | 44 +-- epde/interface/interface.py | 42 ++- epde/operators/common/fitness.py | 78 +++-- epde/operators/common/right_part_selection.py | 217 +++++++++++-- epde/operators/common/sparsity.py | 87 +++++- .../multiobjective/moeadd_specific.py | 125 +++++--- epde/optimizers/moeadd/population_constr.py | 10 +- epde/optimizers/moeadd/strategy.py | 26 +- epde/optimizers/single_criterion/strategy.py | 4 +- epde/supplementary.py | 287 +++++++++++++----- 10 files changed, 690 insertions(+), 230 deletions(-) diff --git a/epde/eq_mo_objectives.py b/epde/eq_mo_objectives.py index 4d459b4f..22a6c963 100644 --- a/epde/eq_mo_objectives.py +++ b/epde/eq_mo_objectives.py @@ -60,37 +60,8 @@ def equation_complexity_by_terms(system, equation_key): return np.count_nonzero(system.vals[equation_key].weights_internal) -def equation_complexity_by_factors(system, equation_key): - ''' - Evaluate the complexity of the system of PDEs, evaluating a number of factors in terms for each - equation. In the evaluation, we consider only terms with non-zero weights and target, while - the free coefficient is not included in the final metric. Also, the real-valued factors are - not considered in the result. - - Parameters: - ----------- - system - ``epde.structure.main_structures.SoEq`` object - The system, that is to be evaluated. - - Returns: - ---------- - discrepancy : list of integers. - The values of the error metric: list entry for each of the equations. - ''' - # eq_compl = 0 - - # for idx, term in enumerate(system.vals[equation_key].structure): - # if idx < system.vals[equation_key].target_idx: - # if not system.vals[equation_key].weights_final[idx] == 0: - # eq_compl += len(term.structure) - # elif idx > system.vals[equation_key].target_idx: - # if not system.vals[equation_key].weights_final[idx-1] == 0: - # eq_compl += len(term.structure) - # else: - # eq_compl += len(term.structure) - # return eq_compl +def _complexity_single_eq(system, equation_key): eq_compl = 0 - for idx, term in enumerate(system.vals[equation_key].structure): if idx < system.vals[equation_key].target_idx: if not system.vals[equation_key].weights_final[idx] == 0: @@ -103,6 +74,19 @@ def equation_complexity_by_factors(system, equation_key): return eq_compl +def equation_complexity_by_factors(system, equation_key=None): + ''' + Evaluate the complexity of the system of PDEs as a number of factors in + non-zero terms for each equation, excluding the free coefficient and + real-valued factors. When ``equation_key`` is None, returns a per-equation + tuple matching the ``system.vars_to_describe`` order; otherwise the scalar + complexity for the named equation. + ''' + if equation_key is None: + return tuple(_complexity_single_eq(system, k) for k in system.vars_to_describe) + return _complexity_single_eq(system, equation_key) + + def equation_terms_stability(system, equation_key = None): if equation_key: assert system.vals[equation_key].stability_calculated diff --git a/epde/interface/interface.py b/epde/interface/interface.py index 306e76e5..9291cd41 100644 --- a/epde/interface/interface.py +++ b/epde/interface/interface.py @@ -230,15 +230,16 @@ class EpdeSearch(object): optimizer_exec_params (`dict`): parameters for execution algorithm of optimization optimizer (`OptimizationPatternDirector`): the strategy of the evolutionary algorithm """ - def __init__(self, multiobjective_mode: bool = True, use_pic = True, use_default_strategy: bool = True, director=None, + def __init__(self, multiobjective_mode: bool = True, use_pic = True, use_default_strategy: bool = True, director=None, director_params: dict = {'variation_params': {}, 'mutation_params': {}, - 'pareto_combiner_params': {}, 'pareto_updater_params': {}}, + 'pareto_combiner_params': {}, 'pareto_updater_params': {}}, time_axis: int = 0, define_domain: bool = True, function_form=None, boundary: int = 0, - use_solver: bool = False, verbose_params: dict = {'show_iter_idx' : True}, + use_solver: bool = False, verbose_params: dict = {'show_iter_idx' : True}, coordinate_tensors=None, memory_for_cache=15, prune_domain: bool = False, - pivotal_tensor_label=None, pruner=None, threshold: float = 1e-2, - division_fractions=3, rectangular: bool = True, - params_filename: str = None, device: str = 'cpu'): + pivotal_tensor_label=None, pruner=None, threshold: float = 1e-2, + division_fractions=3, rectangular: bool = True, + params_filename: str = None, device: str = 'cpu', + fitness_cls=None, sparsity_cls=None): """ Args: multiobjective_mode (`bool`): optional, default True @@ -319,8 +320,9 @@ def __init__(self, multiobjective_mode: bool = True, use_pic = True, use_default self.director = BaselineDirector() builder = StrategyBuilder(EvolutionaryStrategy) self.director.builder = builder - self.director.use_baseline(use_solver=self._mode_info['solver_fitness'], - use_pic=self._use_pic, params=director_params) + self.director.use_baseline(use_solver=self._mode_info['solver_fitness'], + use_pic=self._use_pic, params=director_params, + fitness_cls=fitness_cls, sparsity_cls=sparsity_cls) else: raise NotImplementedError('Wrong arguments passed during the epde search initialization') @@ -360,8 +362,9 @@ def set_moeadd_params(self, population_size: int = 6, solution_params: dict = {} subregion_mating_limitation: float = .95, PBI_penalty: float = 1., training_epochs: int = 100, neighborhood_selector: Callable = simple_selector, - neighborhood_selector_params: tuple = (4,)): - """ + neighborhood_selector_params: tuple = (4,), + early_stopping_callback: Callable = None): + r""" Setting the parameters of the multiobjective evolutionary algorithm. declaration of the default values is held in the initialization of EpdeSearch object. @@ -416,9 +419,10 @@ def set_moeadd_params(self, population_size: int = 6, solution_params: dict = {} 'nds_method' : nds_method, 'ndl_update' : ndl_update_method} - self.optimizer_exec_params = {'epochs' : training_epochs} - - def set_singleobjective_params(self, population_size: int = 4, solution_params: dict = {}, + self.optimizer_exec_params = {'epochs' : training_epochs, + 'early_stopping_callback' : early_stopping_callback} + + def set_singleobjective_params(self, population_size: int = 4, solution_params: dict = {}, sorting_method: Callable = simple_sorting, training_epochs: int = 50): """ Setting parameters for singelobjective optimization. @@ -949,6 +953,18 @@ def cache(self): else: return None, global_var.tensor_cache + @property + def pareto_history(self): + """Per-epoch Pareto-level-0 snapshots, populated during ``fit``. + + Returns a list of length ``training_epochs``; each element is a + list of ``{'text_form': str, 'obj_fun': list}`` dicts -- one per + solution on the non-dominated front at the end of that epoch. + Empty list when the optimizer hasn't been run or doesn't track + epoch history (e.g. single-objective mode). + """ + return getattr(self.optimizer, '_pareto_history', []) + def get_equations_by_complexity(self, complexity : Union[float, list]): ''' Get equations with desired complexity. Works best with ``EpdeSearch.visualize_solutions(...)`` diff --git a/epde/operators/common/fitness.py b/epde/operators/common/fitness.py index 00c21fe7..304b2481 100644 --- a/epde/operators/common/fitness.py +++ b/epde/operators/common/fitness.py @@ -69,17 +69,38 @@ def apply(self, objective: Equation, arguments: dict, force_out_of_place: bool = if force_out_of_place: self.suboperators['sparsity'].apply(objective, subop_args['sparsity']) + # Reject degenerate candidates whose entire non-target library was + # zeroed by sparsity. Without this, ``EqRightPartSelector`` may + # commit a target_idx whose only surviving content is the + # intercept, yielding population members of the form + # ``~0 = u^2 * du/dx0`` (no real LHS) that cannot represent any + # PDE by construction. Mirrors the rejection in ``L2LRFitness`` + # so the LEGACY (L2Fitness) and NEW (L2LRFitness) RPS sweeps + # share the same admissibility criterion. + if all(objective.weights_internal == 0): + return None self.suboperators['coeff_calc'].apply(objective, subop_args['coeff_calc']) _, target, features = objective.evaluate(normalize = False, return_val = False) if features is None: discr_feats = 0 else: - discr_feats = np.dot(features, objective.weights_final[:-1][objective.weights_internal != 0]) + n_cols = features.shape[1] if features.ndim > 1 else 1 + mask = objective.weights_internal != 0 + if n_cols == len(mask): + discr_feats = np.dot(features, objective.weights_internal) + elif n_cols == int(mask.sum()): + discr_feats = np.dot(features, objective.weights_final[:-1]) + else: + discr_feats = np.zeros(features.shape[0]) discr = (discr_feats + np.full(target.shape, objective.weights_final[-1]) - target) - self.g_fun_vals = global_var.grid_cache.g_func_flat - discr = np.multiply(discr, self.g_fun_vals) + try: + self.g_fun_vals = global_var.grid_cache.g_func[global_var.grid_cache.g_func_mask].reshape(-1) + except AttributeError: + self.g_fun_vals = None + if self.g_fun_vals is not None and self.g_fun_vals.shape == discr.shape: + discr = np.multiply(discr, self.g_fun_vals) rl_error = np.linalg.norm(discr, ord = 2) if not (self.params['penalty_coeff'] > 0. and self.params['penalty_coeff'] < 1.): @@ -137,7 +158,21 @@ def apply(self, objective: Equation, arguments: dict, force_out_of_place: bool = if features is None: discr = target - target.mean() else: - discr_feats = np.dot(features, objective.weights_final[:-1]) + # ``features`` width depends on the ``normalize`` flag passed to + # ``evaluate`` above: ``normalize=True`` returns all N-1 + # non-target columns; ``normalize=False`` filters to only the + # nonzero-weight columns. ``weights_final[:-1]`` matches the + # latter shape (nonzero count); ``weights_internal`` matches the + # former (full N-1, with zeros). Pick whichever lines up with + # the actual feature matrix -- same pattern as L2Fitness.apply. + n_cols = features.shape[1] if features.ndim > 1 else 1 + mask = objective.weights_internal != 0 + if n_cols == len(mask): + discr_feats = np.dot(features, objective.weights_internal) + elif n_cols == int(mask.sum()): + discr_feats = np.dot(features, objective.weights_final[:-1]) + else: + discr_feats = np.zeros(features.shape[0]) discr_feats = discr_feats + objective.weights_final[-1] discr = target - discr_feats @@ -155,19 +190,23 @@ def apply(self, objective: Equation, arguments: dict, force_out_of_place: bool = objective.aic_calculated = True data_shape = global_var.grid_cache.inner_shape - if hasattr(objective, '_cached_sw_weights') and objective._cached_sw_weights is not None: - weights = objective._cached_sw_weights + if features is None: + # Degenerate candidate (all features pruned by sparsity). + # Nothing to fit sliding-window weights on -- skip the CV + # calculation and report unit stability so downstream callers + # still get a finite value. + total_lr = 1.0 else: - weights = calculate_weights(features, target, self.g_fun_vals, data_shape, objective.weights_final[-1] != 0) - weights_arr = np.array(weights) - std = weights_arr.std(axis=0, ddof=1) - mu = weights_arr.mean(axis=0) - - # Safe division - with np.errstate(divide='ignore', invalid='ignore'): - cv = (std ** 2) / (mu ** 2) - - total_lr = sum(cv) / len(data_shape) + if hasattr(objective, '_cached_sw_weights') and objective._cached_sw_weights is not None: + weights = objective._cached_sw_weights + else: + weights = calculate_weights(features, target, self.g_fun_vals, data_shape, objective.weights_final[-1] != 0) + weights_arr = np.array(weights) + std = weights_arr.std(axis=0, ddof=1) + mu = weights_arr.mean(axis=0) + with np.errstate(divide='ignore', invalid='ignore'): + cv = (std ** 2) / (mu ** 2) + total_lr = sum(cv) / len(data_shape) if force_out_of_place: return fitness_value * total_lr @@ -357,9 +396,8 @@ def apply(self, objective: SoEq, arguments: dict, force_out_of_place: bool = Fal # Safe division with np.errstate(divide='ignore', invalid='ignore'): cv = (std ** 2) / (mu ** 2) - cv[mu == 0] = 0.0 # Handle zero mean - total_lr = sum(cv[:-1]) / len(data_shape) + total_lr = sum(cv) / len(data_shape) eq.fitness_calculated = True eq.fitness_value = lp @@ -491,8 +529,8 @@ def _compute_stability_for_equation(self, eq: Equation): weights_arr = np.array(weights) std = weights_arr.std(axis=0, ddof=1) mu = weights_arr.mean(axis=0) - cv = np.where(mu != 0, (std / mu) ** 2, 0.0) - total_lr = np.sum(cv[:-1]) / len(data_shape) if len(cv) > 1 else 0.0 + cv = (std ** 2) / (mu ** 2) + total_lr = np.sum(cv) / len(data_shape) eq.coefficients_stability = total_lr eq.stability_calculated = True diff --git a/epde/operators/common/right_part_selection.py b/epde/operators/common/right_part_selection.py index fe3a9772..5081b971 100644 --- a/epde/operators/common/right_part_selection.py +++ b/epde/operators/common/right_part_selection.py @@ -42,24 +42,49 @@ class EqRightPartSelector(CompoundOperator): ''' key = 'FitnessCheckingRightPartSelector' - @HistoryExtender('\n -> The equation structure was detected: ', 'a') + @HistoryExtender('\n -> The equation structure was detected: ', 'a') def apply(self, objective : Equation, arguments : dict): self_args, subop_args = self.parse_suboperator_args(arguments = arguments) - assert len(objective.structure) == len(objective.terms_labels) + # Duplicate-term detection: a frozenset of per-term factor signatures + # has the same length as ``structure`` iff every term is distinct. + # Comparing against ``terms_labels`` here would be dimensionally wrong + # (see the same family of bugs fixed in ``enforce_rps_uniqueness`` and + # ``simplify_equation``). + signatures = {term.factors_labels for term in objective.structure} + assert len(signatures) == len(objective.structure), \ + 'Equation has duplicate terms; randomize before right-part selection.' + outer_max_iter = 50 + inner_max_iter = 100 + outer_attempts = 0 while not (objective.simplified and objective.is_correct_right_part): + outer_attempts += 1 + if outer_attempts > outer_max_iter: + warnings.warn( + 'EqRightPartSelector.apply: outer loop did not converge ' + f'after {outer_max_iter} iterations; accepting current state.' + ) + break objective.reset_state(True) min_fitness = np.inf weights_internal = np.zeros(len(objective.structure) - 1) min_idx = 0 + inner_attempts = 0 while not any(term.contains_deriv(objective.main_var_to_explain) for term in objective.structure): - # while not any(term.contains_deriv() for term in objective.structure): + inner_attempts += 1 + if inner_attempts > inner_max_iter: + warnings.warn( + 'EqRightPartSelector.apply: restore_property failed to ' + f'introduce a deriv of {objective.main_var_to_explain!r} ' + f'after {inner_max_iter} attempts; randomizing equation.' + ) + objective.randomize() + break objective.restore_property(mandatory_family=False, deriv=True) - + for target_idx, target_term in enumerate(objective.structure): if not objective.structure[target_idx].contains_deriv(objective.main_var_to_explain): - # if not objective.structure[target_idx].contains_deriv(): continue objective.target_idx = target_idx fitness = self.suboperators['fitness_calculation'].apply(objective, arguments = subop_args['fitness_calculation'], force_out_of_place = True) @@ -87,11 +112,10 @@ def apply(self, objective : Equation, arguments : dict): if not self.simplify_equation(objective): objective.simplified = True if objective.structure[objective.target_idx].contains_deriv(objective.main_var_to_explain): - # if objective.structure[objective.target_idx].contains_deriv(): objective.is_correct_right_part = True - else: - objective.right_part_selected = True - objective.remove_zero_terms() + + objective.right_part_selected = True + objective.remove_zero_terms() def simplify_equation(self, objective: Equation): # Get nonzero terms @@ -99,7 +123,7 @@ def simplify_equation(self, objective: Equation): nonrs_terms = [term for i, term in enumerate(objective.structure) if i != objective.target_idx] nonzero_terms = [item for item, keep in zip(nonrs_terms, nonzero_terms_mask) if keep] nonzero_terms.append(objective.structure[objective.target_idx]) - equation_terms = [term.term_label_without_power for term in nonzero_terms] + equation_terms = [term.factors_labels_without_power for term in nonzero_terms] # If amount nonzero terms is more than one -- get their intersection if len(equation_terms) > 1: @@ -122,6 +146,7 @@ def simplify_equation(self, objective: Equation): min_order = factor.cache_label[1][0] if len(set(common_dim)) < 2: # If dimension is the same -- reduce order of terms' factor + max_iter = 100 for term in nonzero_terms: factors_simplified = [] for factor in term.structure: @@ -140,10 +165,28 @@ def simplify_equation(self, objective: Equation): term.structure = [factor for factor in term.structure if factor not in factors_simplified] term.reset_saved_state() - # If term's order became zero -- replace term - while len(term.structure) == 0 or not term.contains_meaningful() or len(objective.terms_labels) != len(objective.structure): + # If term's order became zero -- replace term. + # Cap retries so a constrained token pool can't + # deadlock the optimizer (same hazard fixed in + # ``enforce_rps_uniqueness``). + attempts = 0 + while attempts < max_iter: + empty = len(term.structure) == 0 + not_meaningful = not term.contains_meaningful() + signatures = {t.factors_labels for t in objective.structure} + duplicate = len(signatures) != len(objective.structure) + if not (empty or not_meaningful or duplicate): + break term.randomize() + attempts += 1 + # Structure changed: invalidate stale fitness / + # weights / AIC caches while leaving RPS to the + # caller's outer loop. + try: + objective.reset_state(reset_right_part=False) + except TypeError: + objective.reset_state() return True return False @@ -185,17 +228,33 @@ def apply(self, objective : Equation, arguments : dict): if not objective.right_part_selected: term_selection = [term_idx for term_idx, term in enumerate(objective.structure) if term.contains_deriv(variable = objective.main_var_to_explain)] - + if len(term_selection) == 0: idx = np.random.choice([term_idx for term_idx, _ in enumerate(objective.structure)]) prev_term = objective.structure[idx] - while True: + # Bounded retry + dedup check: never spin against a finite + # token pool, never introduce a duplicate term (see + # feedback-structure-dedup memory). + max_iter = 100 + candidate_term = None + for _ in range(max_iter): candidate_term = Term(pool = prev_term.pool, mandatory_family = objective.main_var_to_explain, - max_factors_in_term = len(prev_term.structure), + max_factors_in_term = len(prev_term.structure), create_derivs = True) - if candidate_term.contains_deriv(variable = objective.main_var_to_explain): - break - + if not candidate_term.contains_deriv(variable = objective.main_var_to_explain): + continue + sig = candidate_term.factors_labels + if any(j != idx and t.factors_labels == sig + for j, t in enumerate(objective.structure)): + continue + break + else: + warnings.warn( + f'RandomRHPSelector: could not produce a unique deriv term ' + f'for {objective.main_var_to_explain!r} after {max_iter} ' + f'attempts; keeping last candidate (may duplicate).' + ) + objective.structure[idx] = candidate_term else: idx = np.random.choice(term_selection) @@ -208,3 +267,125 @@ def apply(self, objective : Equation, arguments : dict): def use_default_tags(self): self._tags = {'equation right part selection', 'gene level', 'contains suboperators', 'inplace'} + + +def _scrub_conflicting_terms(equation: Equation, fixed_rps, *, max_iter: int = 100, + skip_idx=None) -> bool: + """Replace any term in ``equation.structure`` whose factor signature is a + superset of one of the ``fixed_rps`` signatures (each a ``frozenset`` of + factor labels). When ``skip_idx`` is passed, the term at that index is left + alone -- used by the bidirectional pass below to preserve an equation's + own already-selected RPS. + + Returns True if at least one term was randomized; the equation's cached + fitness/weight state is reset on the way out. + """ + if not fixed_rps: + return False + + def _conflicts(t): + return any(rs.issubset(t.factors_labels) for rs in fixed_rps) + + changed = False + for idx, term in enumerate(equation.structure): + if idx == skip_idx: + continue + if not _conflicts(term): + continue + for _ in range(max_iter): + term.randomize() + term.reset_saved_state() + signatures = {t.factors_labels for t in equation.structure} + duplicate = len(signatures) != len(equation.structure) + if not _conflicts(term) and not duplicate: + break + changed = True + + if changed: + try: + equation.reset_state(reset_right_part=False) + except TypeError: + equation.reset_state() + return changed + + +class SoEqRightPartSelector(CompoundOperator): + """Chromosome-level RPS that enforces bidirectional cross-equation + uniqueness. + + Forward sequential pass (pre-scrub each equation against + already-selected RPS, then run the per-equation sweep) handles the + case where equation_k > equation_j re-uses equation_j's RPS as a + non-target term. A second bidirectional convergence pass closes the + other direction: equation_j's structure is also scrubbed of any term + whose factor set is a superset of equation_k's (k > j) RPS. Without + the second pass the FIRST equation in ``vars_to_describe`` could keep + a later equation's target as a non-RPS term (e.g. LV's eq for u + keeping ``dv/dx0``), since at the time it was processed the later + RPS was not yet known. + + The bidirectional pass is bounded by ``max_bidirectional_passes`` and + exits as soon as a full sweep produces no scrubbing changes + (fixed-point). Each pass also re-runs the per-equation selector when + its structure changed, since the prior target_idx may no longer be + optimal under the new structure. + """ + key = 'SoEqRightPartSelector' + + def apply(self, objective, arguments: dict): + self_args, subop_args = self.parse_suboperator_args(arguments=arguments) + eq_selector = self.suboperators['eq_right_part_selector'] + eq_args = subop_args.get('eq_right_part_selector', arguments) + + equations = list(objective) + rps_signatures = [None] * len(equations) + + # Forward sequential pass: pre-scrub each equation against + # already-fixed RPS signatures, then run the per-equation selector. + for eq_idx, equation in enumerate(equations): + other_rps = [rs for rs in rps_signatures[:eq_idx] if rs is not None] + if other_rps: + _scrub_conflicting_terms(equation, other_rps, max_iter=100) + eq_selector.apply(objective=equation, arguments=eq_args) + try: + rps_signatures[eq_idx] = equation.structure[ + equation.target_idx].factors_labels + except (AttributeError, IndexError, TypeError): + rps_signatures[eq_idx] = None + + # Bidirectional convergence: each equation now knows the others' + # RPS, so re-scrub against the full set (skipping own target) and + # re-select when scrubbing changes the structure. Iterates until + # a full pass yields no changes. + max_passes = 5 + for _ in range(max_passes): + any_changes = False + for eq_idx, equation in enumerate(equations): + other_rps = [rs for i, rs in enumerate(rps_signatures) + if i != eq_idx and rs is not None] + if not other_rps: + continue + target_idx = getattr(equation, 'target_idx', None) + changed = _scrub_conflicting_terms( + equation, other_rps, max_iter=100, skip_idx=target_idx, + ) + if not changed: + continue + # Scrubbing mutated non-target terms: force re-selection so + # the post-scrub structure is evaluated for the best RPS. + equation.right_part_selected = False + equation.simplified = False + equation.is_correct_right_part = False + eq_selector.apply(objective=equation, arguments=eq_args) + try: + rps_signatures[eq_idx] = equation.structure[ + equation.target_idx].factors_labels + except (AttributeError, IndexError, TypeError): + pass + any_changes = True + if not any_changes: + break + + def use_default_tags(self): + self._tags = {'right part selection', 'chromosome level', + 'contains suboperators', 'inplace'} diff --git a/epde/operators/common/sparsity.py b/epde/operators/common/sparsity.py index 08539305..a510dc59 100644 --- a/epde/operators/common/sparsity.py +++ b/epde/operators/common/sparsity.py @@ -7,6 +7,8 @@ """ import numpy as np +from sklearn.linear_model import Lasso + import epde.globals as global_var from epde.operators.utils.template import CompoundOperator from epde.structure.main_structures import Equation @@ -14,10 +16,7 @@ from sklearn.base import BaseEstimator, RegressorMixin # import seaborn as sns import matplotlib.pyplot as plt -from epde.supplementary import calculate_weights - -import numpy as np -from sklearn.base import BaseEstimator, RegressorMixin +from epde.supplementary import calculate_weights, GramSetup # class PhysicsInformedLasso(BaseEstimator, RegressorMixin): @@ -226,7 +225,8 @@ def get_cv(self, weights): mu = weights_arr.mean(axis=0) with np.errstate(divide='ignore', invalid='ignore'): - cv = (std ** 2) / (mu ** 2 + std ** 2) + cv = std ** 2 / mu ** 2 + # cv = std ** 2 return np.nan_to_num(cv) @@ -254,6 +254,13 @@ def fit(self, X, y, sample_weights=None): norm_sq_features = np.sum(X_aug ** 2, axis=0) X_T_y = X_aug.T @ y # Cached once; slice by active_mask each outer iter. + # Pre-build the full sliding-window Gram matrix ONCE. The outer + # RFE loop below will slice it by ``active_mask`` per iteration + # instead of re-running the expensive ``X^T diag(w) X`` matmul on + # the surviving columns. The math is exact: a sub-block of the + # full Gram equals the Gram of the corresponding sub-columns. + gram_setup = GramSetup(X, y, sample_weights, self.grid_shape) + outer_iteration = 0 max_outer_iters = total_features # Max possible eliminations @@ -266,14 +273,9 @@ def fit(self, X, y, sample_weights=None): surviving_features_mask = active_mask[:-1] intercept_is_active = active_mask[-1] - # 2. Calculate physical priors ONLY for the active library - weights = calculate_weights( - X[:, surviving_features_mask], - y, - sample_weights=sample_weights, - grid_shape=self.grid_shape, - fit_intercept=intercept_is_active - ) + # 2. Calculate physical priors ONLY for the active library -- + # slice the precomputed full Gram by the current active mask. + weights = gram_setup.solve(active_mask) # Slice data for the CD run X_active = X_aug[:, active_mask] @@ -289,6 +291,7 @@ def fit(self, X, y, sample_weights=None): # terms get shrunk to zero before they pollute the residual. cv_order = np.argsort(active_cv)[::-1] active_thresholds = active_cv * max_corr + # active_thresholds = active_cv * norm_sq_active # Initialize coefficients active_coef = weights.mean(axis=0) @@ -423,6 +426,58 @@ def apply(self, objective : Equation, arguments : dict): # print(f'Metaparameter: {objective.metaparameters}, objective.metaparameters[("sparsity", objective.main_var_to_explain)]') self_args, subop_args = self.parse_suboperator_args(arguments = arguments) + estimator = Lasso(alpha=objective.metaparameters[('sparsity', objective.main_var_to_explain)]['value'], + copy_X=True, fit_intercept=True, max_iter=1000, + positive=False, precompute=False, random_state=None, + selection='random', tol=0.0001, warm_start=False) + + _, target, features = objective.evaluate(normalize = True, return_val = False) + + self.g_fun_vals = global_var.grid_cache.g_func[global_var.grid_cache.g_func_mask] + + n_features = features.shape[1] if (features is not None and hasattr(features, 'ndim') and features.ndim > 1) else 0 + if features is None or not np.all(np.isfinite(features)) or not np.all(np.isfinite(target)): + # Degenerate features (e.g. constant column triggering divide-by-zero + # in objective.evaluate's min-max normalisation). Fall back to a + # zero-weight assignment so the candidate is treated as "empty" + # rather than aborting the whole optimisation run. + coef = np.zeros(n_features) + intercept = 0.0 + else: + estimator.fit(features, target, self.g_fun_vals) + coef = estimator.coef_ + intercept = estimator.intercept_ + objective.weights_internal = coef + objective.weights_internal_evald = True + objective.weights_final = np.append([weight for weight in coef if weight != 0], intercept) + objective.weights_final_evald = True + # objective._cached_sw_weights = estimator.cached_weights_ + # Note: _eval_cache is intentionally NOT wiped here. The cache stores + # (value, target, features) tuples keyed on (normalize, return_val, + # grids is None); none of those depend on the weights this operator + # just updated. Structural mutations call ``Equation.reset_state`` + # which performs the wipe at the right moment. + + + def use_default_tags(self): + self._tags = {'sparsity', 'gene level', 'no suboperators', 'inplace'} + + +class VWSRSparsity(CompoundOperator): + """ + Variance-Weighted Sparse Regression operator. + + Mirrors :class:`LASSOSparsity` but swaps the sklearn ``Lasso`` estimator + for :class:`PhysicsInformedLasso`, which derives feature-specific L1 + penalties from the squared coefficient of variation of sliding-window + fits. Used as the regression step of the "new" pipeline in the EPDE + within-platform comparison (thesis Section 4.5). + """ + key = 'VWSRBasedSparsity' + + def apply(self, objective : Equation, arguments : dict): + self_args, subop_args = self.parse_suboperator_args(arguments = arguments) + estimator = PhysicsInformedLasso(grid_shape=global_var.grid_cache.inner_shape) _, target, features = objective.evaluate(normalize = True, return_val = False) @@ -435,10 +490,10 @@ def apply(self, objective : Equation, arguments : dict): objective.weights_final = np.append([weight for weight in estimator.coef_ if weight != 0], estimator.intercept_) objective.weights_final_evald = True objective._cached_sw_weights = estimator.cached_weights_ - objective._eval_cache = {} - + # See LASSOSparsity.apply: _eval_cache survives a weights update; + # only structural resets via ``Equation.reset_state`` should wipe it. def use_default_tags(self): self._tags = {'sparsity', 'gene level', 'no suboperators', 'inplace'} - + diff --git a/epde/operators/multiobjective/moeadd_specific.py b/epde/operators/multiobjective/moeadd_specific.py index 7ae77295..6ca224bb 100644 --- a/epde/operators/multiobjective/moeadd_specific.py +++ b/epde/operators/multiobjective/moeadd_specific.py @@ -29,8 +29,19 @@ def penalty_based_intersection(sol_obj, weight, ideal_obj, ''' solution_objective = sol_obj.obj_fun if obj_normalizer is None else obj_normalizer(sol_obj.obj_fun) - weight_full = np.array([item for item in weight for _ in sol_obj.vals]) - ideal_obj_full = np.array([item for item in ideal_obj for _ in sol_obj.vals]) + weight_arr = np.asarray(weight) + ideal_obj_arr = np.asarray(ideal_obj) + n_eqs = len(sol_obj.vals) + n_obj = solution_objective.shape[0] + if weight_arr.size * n_eqs == n_obj: + # MOEA/D weight is per objective TYPE -- expand to per-equation space. + weight_full = np.repeat(weight_arr, n_eqs) + ideal_obj_full = np.repeat(ideal_obj_arr, n_eqs) + else: + # Weight already lives in the full objective space (legacy + # objective list of per-equation partials). + weight_full = weight_arr + ideal_obj_full = ideal_obj_arr weight_norm = np.linalg.norm(weight_full) @@ -106,7 +117,7 @@ def locate_pareto_worst(levels, weights: np.ndarray, best_obj: np.ndarray, penal # NOTE: If your solution objects have a `.rank` or `.ndl` attribute, # replace this inner loop entirely with: `domain_solution_NDL_idxs[solution_idx] = solution.rank` for level_idx, level in enumerate(levels.levels): - if any(solution.terms_labels == level_solution.terms_labels for level_solution in level): + if any(solution.equations_labels == level_solution.equations_labels for level_solution in level): domain_solution_NDL_idxs[solution_idx] = level_idx break @@ -360,18 +371,13 @@ def apply(self, objective: ParetoLevels, arguments: dict): temp_offspring = self.suboperators['chromosome_mutation'].apply(objective=temp_offspring, arguments=subop_args['chromosome_mutation']) temp_offspring.reset_state(True) + # SoEqRightPartSelector enforces cross-equation RPS + # uniqueness inline (sequential pre-scrub), so no post-hoc + # ``enforce_rps_uniqueness`` retry loop is needed here. self.suboperators['right_part_selector'].apply(objective=temp_offspring, arguments=subop_args['right_part_selector']) - if len(temp_offspring.vars_to_describe) > 1: - term_replaced = is_rps_in_other_equation(temp_offspring) - while any(term_replaced): - temp_offspring.reset_state(True) - self.suboperators['right_part_selector'].apply(objective=temp_offspring, - arguments=subop_args['right_part_selector']) - term_replaced = is_rps_in_other_equation(temp_offspring) - - system = temp_offspring.terms_labels + system = temp_offspring.equations_labels if system not in objective.history: self.suboperators['chromosome_fitness'].apply(objective=temp_offspring, arguments=subop_args['chromosome_fitness']) @@ -437,32 +443,18 @@ def apply(self, objective : ParetoLevels, arguments : dict): if len(objective.population) == 0: for idx, candidate in enumerate(objective.unplaced_candidates): candidate.reset_state(True) + # SoEqRightPartSelector handles cross-equation RPS + # uniqueness inline; no post-hoc retry needed. self.suboperators['right_part_selector'].apply(objective = candidate, arguments = subop_args['right_part_selector']) - if len(candidate.vars_to_describe) > 1: - replaced = is_rps_in_other_equation(candidate) - while any(replaced): - candidate.reset_state(True) - self.suboperators['right_part_selector'].apply(objective=candidate, - arguments=subop_args['right_part_selector']) - replaced = is_rps_in_other_equation(candidate) - - system = candidate.terms_labels + + system = candidate.equations_labels while system in objective.history: candidate.create() candidate.reset_state(True) self.suboperators['right_part_selector'].apply(objective=candidate, arguments=subop_args['right_part_selector']) - - if len(candidate.vars_to_describe) > 1: - replaced = is_rps_in_other_equation(candidate) - while any(replaced): - candidate.reset_state(True) - self.suboperators['right_part_selector'].apply(objective=candidate, - arguments=subop_args['right_part_selector']) - replaced = is_rps_in_other_equation(candidate) - - system = candidate.terms_labels + system = candidate.equations_labels self.suboperators['chromosome_fitness'].apply(objective=candidate, arguments=subop_args['chromosome_fitness']) objective.history.add(system) @@ -503,20 +495,61 @@ def has_subset_pair(collection_of_sets): # No subset relationship found among any pairs return False, None, None -def is_rps_in_other_equation(objective): - rsterms = [None for _ in objective.vals] - replaced = [False for _ in objective.vals] - for equation_idx, equation in enumerate(objective.vals): - rsterms[equation_idx] = equation.structure[equation.target_idx].term_label +def _debug_assert_rps_unique(objective) -> list: + """Debug helper: scan an SoEq's equations and return a per-equation + list of bools indicating which equations contain at least one + non-target term whose factor set is a superset of another equation's + target term factor set. + + The post-hoc enforcement loop that used to call this and rewrite + conflicting terms is gone -- ``SoEqRightPartSelector`` now propagates + the uniqueness constraint forward across equations during the RPS + sweep itself, so a correctly-implemented pipeline must produce an + all-False result here. Use this in tests or temporary asserts to + catch regressions; do NOT wire it back into the operator graph as a + repair step. + """ + equations = list(objective.vals) + rsterms = [eq.structure[eq.target_idx].factors_labels for eq in equations] + flagged = [False] * len(equations) - for equation_idx, equation in enumerate(objective.vals): - rs = rsterms[:equation_idx] + rsterms[equation_idx + 1:] + for eq_idx, equation in enumerate(equations): + other_rs = rsterms[:eq_idx] + rsterms[eq_idx + 1:] for term_idx, term in enumerate(equation.structure): - if any(rsterm.issubset(term.term_label) for rsterm in rs): - replaced[equation_idx] = True - term.randomize() - term.reset_saved_state() - while any(rsterm.issubset(term.term_label) for rsterm in rs) or len(equation.terms_labels) != len(equation.structure): - term.randomize() - term.reset_saved_state() - return replaced \ No newline at end of file + if term_idx == equation.target_idx: + continue + if any(rs.issubset(term.factors_labels) for rs in other_rs): + flagged[eq_idx] = True + break + return flagged + + +def is_rps_in_other_equation(objective): + """Deprecated alias. The post-hoc uniqueness repair has been replaced + by ``SoEqRightPartSelector`` (sequential pre-scrub), so this is now a + pure assertion helper that returns a per-equation flag list without + mutating anything. External callers should migrate to using the new + operator and remove their ``while any(is_rps_in_other_equation(...))`` + retry loops; the new operator guarantees the result is all-False on a + well-formed SoEq. + """ + warnings.warn( + 'is_rps_in_other_equation is now a pure debug check; ' + 'SoEqRightPartSelector enforces uniqueness during RPS dispatch. ' + 'Drop your retry loop.', + DeprecationWarning, stacklevel=2, + ) + return _debug_assert_rps_unique(objective) + + +def enforce_rps_uniqueness(objective, *, max_iter: int = 100) -> list: + """Deprecated. Retained as an assertion-only shim for code that + imports the old name; mutates nothing. Wraps + :func:`_debug_assert_rps_unique`. + """ + warnings.warn( + 'enforce_rps_uniqueness is now a pure debug check; ' + 'SoEqRightPartSelector enforces uniqueness during RPS dispatch.', + DeprecationWarning, stacklevel=2, + ) + return _debug_assert_rps_unique(objective) \ No newline at end of file diff --git a/epde/optimizers/moeadd/population_constr.py b/epde/optimizers/moeadd/population_constr.py index 0c98f7b0..950d6339 100644 --- a/epde/optimizers/moeadd/population_constr.py +++ b/epde/optimizers/moeadd/population_constr.py @@ -39,9 +39,9 @@ def applyToPassed(self, passed_solution: SoEq, **kwargs): passed_solution.use_default_multiobjective_function(self.use_pic) def create(self, **kwargs): - # sparsity = kwargs.get('sparsity', 10 ** (np.random.uniform(low = np.log10(self.sparsity_interval[0]), - # high = np.log10(self.sparsity_interval[1]), - # size = len(self.vars_demand_equation)))) + sparsity = kwargs.get('sparsity', 10 ** (np.random.uniform(low = np.log10(self.sparsity_interval[0]), + high = np.log10(self.sparsity_interval[1]), + size = len(self.vars_demand_equation)))) # # nonzero_terms = kwargs.get('nonzero_terms', np.random.randint(low=1, # high=self.terms_number, # size=len(self.vars_demand_equation))) @@ -57,8 +57,8 @@ def create(self, **kwargs): # print(f'Creating new equation, sparsity value {sparsity}') metaparameters = {'terms_number' : {'optimizable' : False, 'value' : terms_number}, 'max_factors_in_term' : {'optimizable' : False, 'value' : max_factors_in_term}} - # for idx, variable in enumerate(self.vars_demand_equation): - # metaparameters[('sparsity', variable)] = {'optimizable' : True, 'value' : sparsity[idx]} + for idx, variable in enumerate(self.vars_demand_equation): + metaparameters[('sparsity', variable)] = {'optimizable' : True, 'value' : sparsity[idx]} # metaparameters[('nonzero_terms', variable)] = {'optimizable': True, 'value': nonzero_terms[idx]} # metaparameters[('threshold', variable)] = {'optimizable': True, 'value': threshold[idx]} # metaparameters[('nu', variable)] = {'optimizable': True, 'value': nu[idx]} diff --git a/epde/optimizers/moeadd/strategy.py b/epde/optimizers/moeadd/strategy.py index 8fd0281d..5774f981 100644 --- a/epde/optimizers/moeadd/strategy.py +++ b/epde/optimizers/moeadd/strategy.py @@ -15,10 +15,10 @@ from epde.operators.multiobjective.selections import MOEADDSelection from epde.operators.multiobjective.variation import get_basic_variation from epde.operators.common.fitness import L2Fitness, L2LRFitness, SolverBasedFitness, PIC, DeepXDEBasedFitness -from epde.operators.common.right_part_selection import RandomRHPSelector, EqRightPartSelector +from epde.operators.common.right_part_selection import RandomRHPSelector, EqRightPartSelector, SoEqRightPartSelector from epde.operators.multiobjective.moeadd_specific import get_pareto_levels_updater, SimpleNeighborSelector, get_initial_sorter -from epde.operators.common.sparsity import LASSOSparsity +from epde.operators.common.sparsity import LASSOSparsity, VWSRSparsity from epde.operators.common.coeff_calculation import LinRegBasedCoeffsEquation from epde.optimizers.builder import add_sequential_operators, OptimizationPatternDirector, StrategyBuilder @@ -28,7 +28,9 @@ class MOEADDDirector(OptimizationPatternDirector): """ Class for creating strategy builder of multicriterian optimization """ - def use_baseline(self, use_solver: bool = False, use_pic: bool = True, variation_params : dict = {}, mutation_params : dict = {}, + def use_baseline(self, use_solver: bool = False, use_pic: bool = True, + fitness_cls=None, sparsity_cls=None, + variation_params : dict = {}, mutation_params : dict = {}, sorter_params : dict = {}, pareto_combiner_params : dict = {}, pareto_updater_params : dict = {}, **kwargs): add_kwarg_to_operator = partial(add_base_param_to_operator, target_dict = kwargs) @@ -44,8 +46,8 @@ def use_baseline(self, use_solver: bool = False, use_pic: bool = True, variation # right_part_selector = RandomRHPSelector() right_part_selector = EqRightPartSelector() - - sparsity = LASSOSparsity() + + sparsity = (sparsity_cls if sparsity_cls is not None else VWSRSparsity)() coeff_calc = LinRegBasedCoeffsEquation() if use_solver: @@ -56,9 +58,9 @@ def use_baseline(self, use_solver: bool = False, use_pic: bool = True, variation sparsity_c = map_operator_between_levels(sparsity, 'gene level', 'chromosome level') coeff_calc_c = map_operator_between_levels(coeff_calc, 'gene level', 'chromosome level') else: - sparsity_c = sparsity; coeff_calc_c = coeff_calc + sparsity_c = sparsity; coeff_calc_c = coeff_calc - fitness = L2LRFitness(['penalty_coeff']) + fitness = (fitness_cls if fitness_cls is not None else L2LRFitness)(['penalty_coeff']) add_kwarg_to_operator(operator = fitness) fitness.set_suboperators({'sparsity' : sparsity_c, 'coeff_calc' : coeff_calc_c}) @@ -77,9 +79,15 @@ def use_baseline(self, use_solver: bool = False, use_pic: bool = True, variation sparsity_c = map_operator_between_levels(sparsity, 'gene level', 'chromosome level') + # Chromosome-level RPS that threads "already-fixed RPS signatures" + # across the equations of a SoEq, pre-scrubbing each later + # equation's structure so the post-hoc enforce_rps_uniqueness check + # becomes unnecessary (the entire SoEq comes out uniqueness-clean + # by construction). + sys_rps_inner = SoEqRightPartSelector() + sys_rps_inner.set_suboperators({'eq_right_part_selector': right_part_selector}) rps_cond = lambda x: any([not elem_eq.right_part_selected for elem_eq in x.vals]) - sys_rps = map_operator_between_levels(right_part_selector, 'gene level', 'chromosome level', - objective_condition=rps_cond) + sys_rps = OperatorCondition(sys_rps_inner, rps_cond) # Separate mutation from population updater for better customization. initial_sorter = get_initial_sorter(right_part_selector = sys_rps, chromosome_fitness = fitness, diff --git a/epde/optimizers/single_criterion/strategy.py b/epde/optimizers/single_criterion/strategy.py index 58bc578e..c6c73846 100644 --- a/epde/optimizers/single_criterion/strategy.py +++ b/epde/optimizers/single_criterion/strategy.py @@ -7,7 +7,7 @@ from epde.operators.common.right_part_selection import RandomRHPSelector from epde.operators.common.fitness import L2Fitness -from epde.operators.common.sparsity import LASSOSparsity +from epde.operators.common.sparsity import LASSOSparsity, VWSRSparsity from epde.operators.common.coeff_calculation import LinRegBasedCoeffsEquation from epde.operators.singleobjective.mutations import get_singleobjective_mutation from epde.operators.singleobjective.variation import get_singleobjective_variation @@ -38,7 +38,7 @@ def use_baseline(self, params: dict, **kwargs): selection = RouletteWheelSelection(['parents_fraction']) add_kwarg_to_operator(operator = selection) - sparsity = LASSOSparsity() + sparsity = VWSRSparsity() coeff_calc = LinRegBasedCoeffsEquation() eq_fitness = L2Fitness(['penalty_coeff']) add_kwarg_to_operator(operator = eq_fitness) diff --git a/epde/supplementary.py b/epde/supplementary.py index cf8da907..69cd6ac7 100644 --- a/epde/supplementary.py +++ b/epde/supplementary.py @@ -281,17 +281,17 @@ def detect_similar_terms(base_equation_1, base_equation_2): different_terms = all_first_equation_terms.symmetric_difference(all_second_equation_terms) for term in base_equation_1.structure: - if term.term_label in common_terms: + if term.factors_labels in common_terms: same_terms_from_eq1.append(term) - elif term.term_label in (all_first_equation_terms - all_second_equation_terms): + elif term.factors_labels in (all_first_equation_terms - all_second_equation_terms): similar_terms_from_eq1.append(term) else: different_terms_from_eq1.append(term) for term in base_equation_2.structure: - if term.term_label in common_terms: + if term.factors_labels in common_terms: same_terms_from_eq2.append(term) - elif term.term_label in (all_second_equation_terms - all_first_equation_terms): + elif term.factors_labels in (all_second_equation_terms - all_first_equation_terms): similar_terms_from_eq2.append(term) else: different_terms_from_eq2.append(term) @@ -393,76 +393,221 @@ def minmax_normalize(matrix): return matrix -def calculate_weights(X, y, sample_weights, grid_shape, fit_intercept=True): +def _cholesky_solve_batched(A, b): + """Solve ``A @ x = b`` batched over the leading axis using Cholesky. + + ``A`` is assumed symmetric positive-definite (shape ``(batch, n, n)``); + ``b`` is the RHS ``(batch, n, 1)``. Returns ``(x, L)`` where ``x`` is + the solution and ``L`` is the lower-triangular factor (so the caller + can reuse it for iterative refinement). If Cholesky fails on any batch + entry, returns ``(None, None)`` to signal "use the lstsq fallback". + + numpy doesn't ship a batched triangular solver, so the two triangular + solves go through ``np.linalg.solve`` -- still SPD-stable and ~1.5x + faster than feeding the full ``A`` to ``np.linalg.solve``. """ - Vectorized calculation of weights across sliding windows. - Dynamically handles whether the intercept should be fit. + try: + L = np.linalg.cholesky(A) + except np.linalg.LinAlgError: + return None, None + try: + z = np.linalg.solve(L, b) + x = np.linalg.solve(L.transpose(0, 2, 1), z) + except np.linalg.LinAlgError: + return None, L + return x, L + + +def _per_batch_lstsq(A, b): + """Per-batch SVD-based least-squares solve. Used as the safety net + when Cholesky reports the equilibrated batch is non-SPD. Returns + weights of shape ``(batch, n, 1)`` matching the input RHS layout so + the caller can compose with subsequent matrix products without + reshaping. + """ + batch_size = A.shape[0] + n = A.shape[1] + out = np.empty((batch_size, n, 1)) + for i in range(batch_size): + sol, *_ = np.linalg.lstsq(A[i], b[i, :, 0], rcond=None) + out[i, :, 0] = sol + return out + + +class GramSetup: + """Precomputed batched normal-equation matrices for fast active-mask + solves. Splits :func:`calculate_weights` into a setup phase (compute + ``X^T diag(w) X`` and ``X^T diag(w) y`` per window-batch per dimension, + using the FULL augmented feature matrix) and a solve phase (slice each + full Gram matrix by an active-feature mask and solve). The setup is + mask-independent; only the solve depends on which columns are active. + + Used by :class:`PhysicsInformedLasso.fit`, whose outer RFE loop calls + ``calculate_weights`` per shrinking column subset. With this split the + expensive ``X^T diag(w) X`` matmul runs ONCE per fit and each outer + iter only pays the cost of an (active × active) solve. The math is + exact: a sub-block of a Gram matrix equals the Gram of the + corresponding sub-columns. """ - n_samples, n_features = X.shape - # 1. Augment X with intercept ONLY if it is currently active - if fit_intercept: + def __init__(self, X, y, sample_weights, grid_shape): + n_samples = X.shape[0] + # Always augment X with the intercept column so callers can toggle + # ``fit_intercept`` via the active mask's last bit rather than + # re-running setup. X_aug = np.hstack([X, np.ones((n_samples, 1))]) - else: - X_aug = X # Use raw X directly - - n_features_aug = X_aug.shape[1] - - # 2. Reshape to spatial grid - X_grid = X_aug.reshape(*grid_shape, n_features_aug) - y_grid = y.reshape(*grid_shape) - sample_weights_grid = sample_weights.reshape(*grid_shape) - - all_weights = [] - - # 3. Iterate over dimensions - for dim in range(len(grid_shape)): - window_size = grid_shape[dim] // 2 - num_horizons = window_size + 1 - step_size = max(1, num_horizons // 30) - - # --- Create Sliding Windows (Zero Copy) --- - X_windows = sliding_window_view(X_grid, window_shape=window_size, axis=dim) - y_windows = sliding_window_view(y_grid, window_shape=window_size, axis=dim) - w_windows = sliding_window_view(sample_weights_grid, window_shape=window_size, axis=dim) - - # Apply step size stride - X_windows = X_windows.take(indices=range(0, num_horizons, step_size), axis=dim) - y_windows = y_windows.take(indices=range(0, num_horizons, step_size), axis=dim) - w_windows = w_windows.take(indices=range(0, num_horizons, step_size), axis=dim) - - # --- Reshape for Batch Regression --- - X_windows = np.moveaxis(X_windows, dim, 0) - y_windows = np.moveaxis(y_windows, dim, 0) - w_windows = np.moveaxis(w_windows, dim, 0) - - X_windows = np.moveaxis(X_windows, -2, -1) - - # Flatten spatial dimensions - batch_size = X_windows.shape[0] - X_batch = X_windows.reshape(batch_size, -1, n_features_aug) - y_batch = y_windows.reshape(batch_size, -1) - weights_batch = w_windows.reshape(batch_size, -1, 1) - - # --- Solve Normal Equations (Batch Mode) --- - XTW = X_batch.transpose(0, 2, 1) * weights_batch.transpose(0, 2, 1) - XTWX = XTW @ X_batch - XTWy = XTW @ y_batch[..., None] - - # Dynamic ridge penalty based on current active features - ridge = 1e-6 * np.eye(n_features_aug) - XTWX += ridge - - # 2. Solve (Fast CPU Vectorized Solver) - try: - w_batch = np.linalg.solve(XTWX, XTWy) - all_weights.append(w_batch.squeeze(-1)) - except np.linalg.LinAlgError: - w_batch = np.linalg.lstsq(XTWX, XTWy, rcond=None)[0] - # lstsq returns 2D array if targets are 1D, so check shape - if w_batch.ndim == 3: - all_weights.append(w_batch.squeeze(-1)) + n_features_aug = X_aug.shape[1] + + X_grid = X_aug.reshape(*grid_shape, n_features_aug) + y_grid = y.reshape(*grid_shape) + sample_weights_grid = sample_weights.reshape(*grid_shape) + + self.n_features_aug = n_features_aug + self.grid_shape = grid_shape + self._per_dim = [] + + for dim in range(len(grid_shape)): + window_size = grid_shape[dim] // 2 + num_horizons = window_size + 1 + step_size = max(1, num_horizons // 30) + + X_windows = sliding_window_view(X_grid, window_shape=window_size, axis=dim) + y_windows = sliding_window_view(y_grid, window_shape=window_size, axis=dim) + w_windows = sliding_window_view(sample_weights_grid, window_shape=window_size, axis=dim) + + X_windows = X_windows.take(indices=range(0, num_horizons, step_size), axis=dim) + y_windows = y_windows.take(indices=range(0, num_horizons, step_size), axis=dim) + w_windows = w_windows.take(indices=range(0, num_horizons, step_size), axis=dim) + + X_windows = np.moveaxis(X_windows, dim, 0) + y_windows = np.moveaxis(y_windows, dim, 0) + w_windows = np.moveaxis(w_windows, dim, 0) + X_windows = np.moveaxis(X_windows, -2, -1) + + batch_size = X_windows.shape[0] + X_batch = X_windows.reshape(batch_size, -1, n_features_aug) + y_batch = y_windows.reshape(batch_size, -1) + weights_batch = w_windows.reshape(batch_size, -1, 1) + + XTW = X_batch.transpose(0, 2, 1) * weights_batch.transpose(0, 2, 1) + XTWX_full = XTW @ X_batch + XTWy_full = XTW @ y_batch[..., None] + + # Per-batch column scales for equilibration in :meth:`solve`. + # ``diag`` is the per-feature L2 norm squared (weighted) of the + # underlying X columns; ``sqrt`` brings it back to a column- + # norm scale. The ``1e-30`` floor is a degenerate-column guard + # (well below any meaningful data scale) so ``1/scale`` stays + # finite for near-zero columns. + diag = np.diagonal(XTWX_full, axis1=1, axis2=2) + scales = np.sqrt(np.maximum(np.abs(diag), 1e-30)) + + self._per_dim.append((XTWX_full, XTWy_full, scales)) + + def solve(self, active_mask=None, ridge_rel=None, ridge_floor=None): + """Solve the normal equations for the active-feature subset across + every window-batch in every spatial dimension. ``active_mask`` is a + length-``n_features_aug`` boolean array; pass ``None`` for the full + set (equivalent to the legacy ``fit_intercept=True`` path). Returns + weights of shape ``(total_windows_across_dims, active_count)``. + + Stability strategy (preserves the Gram-sub-block precompute trick): + + 1. **Column equilibration**: rescale columns by + ``1/sqrt(diag(XTWX))`` so the equilibrated Gram has unit + diagonals and a much smaller effective condition number than + the raw ``XTWX`` (which carries the squared condition number + of the underlying ``sqrt(W) X``). + 2. **Cholesky on the equilibrated SPD batch** (with batched LU + fallback if scipy's batched triangular solve isn't available + on this numpy). Cholesky has tighter backward error than LU + and is ~2x faster on SPD inputs. + 3. **One step of iterative refinement** on the original (un- + equilibrated) system, recovering 6-8 decimal digits that + normal-equation conditioning costs. + 4. **Per-batch lstsq safety net** for any window-batch where + Cholesky fails (non-SPD after equilibration -- rare). + + ``ridge_rel`` / ``ridge_floor`` are kept as no-op kwargs for + backward compatibility with callers from the previous adaptive- + ridge era; the equilibrated solve does not need a per-feature + ridge, only a tiny flat ``1e-10`` on the unit-diagonal matrix. + """ + if active_mask is None: + active_mask = np.ones(self.n_features_aug, dtype=bool) + active_size = int(active_mask.sum()) + + all_weights = [] + for XTWX_full, XTWy_full, scales_full in self._per_dim: + # Two-step boolean slice. Boolean indexing copies, so the + # result is a fresh array we can modify in place without + # corrupting the cached full Gram. + XTWX_a = XTWX_full[:, active_mask, :][:, :, active_mask] + XTWy_a = XTWy_full[:, active_mask, :] + s_a = scales_full[:, active_mask] # (batch, k) + inv_s = 1.0 / s_a # (batch, k) + + # Equilibrate: A = D^-1 XTWX D^-1, b = D^-1 XTWy. After this + # the diagonal of A is 1 by construction; the off-diagonals + # are the correlation coefficients between the underlying + # columns of sqrt(W) X. + A = XTWX_a * inv_s[:, :, None] * inv_s[:, None, :] + b = XTWy_a * inv_s[:, :, None] + + # Tiny flat ridge on the equilibrated diagonal (now ~1 by + # construction) to keep Cholesky well-defined when columns + # are exactly collinear. + idx = np.arange(active_size) + A[:, idx, idx] += 1e-10 + + batch_size = A.shape[0] + w_norm, L = _cholesky_solve_batched(A, b) + if w_norm is None: + # Cholesky failed somewhere in the batch; per-entry + # lstsq safety net on the equilibrated system. + w_norm = _per_batch_lstsq(A, b) + + # Iterative refinement on the ORIGINAL system to claw back + # digits lost to normal-equation condition squaring. + # w0 = D^-1 w_norm is the candidate solution in original + # coordinates; the residual r = XTWy - XTWX @ w0 measures + # how much it misses the original equation; the correction + # dw_norm solves the same equilibrated system on D^-1 r and + # is unscaled back to dw. + w0 = w_norm * inv_s[:, :, None] + r = XTWy_a - XTWX_a @ w0 + r_norm = r * inv_s[:, :, None] + if L is not None: + try: + z = np.linalg.solve(L, r_norm) + dw_norm = np.linalg.solve(L.transpose(0, 2, 1), z) + except np.linalg.LinAlgError: + dw_norm = _per_batch_lstsq(A, r_norm) else: - all_weights.append(w_batch) + dw_norm = _per_batch_lstsq(A, r_norm) + w = w0 + dw_norm * inv_s[:, :, None] + + all_weights.append(w.squeeze(-1)) + return np.vstack(all_weights) - return np.vstack(all_weights) + +def calculate_weights(X, y, sample_weights, grid_shape, fit_intercept=True): + """ + Vectorized calculation of weights across sliding windows. + Dynamically handles whether the intercept should be fit. + + Single-shot wrapper over :class:`GramSetup`: builds the precomputed + Gram once and immediately solves with the requested intercept policy. + Callers that solve the same Gram against many active masks (e.g. + :class:`PhysicsInformedLasso.fit`) should instantiate ``GramSetup`` + directly and call ``.solve(active_mask)`` per iteration to avoid + re-running the expensive ``X^T diag(w) X`` matmul. + """ + setup = GramSetup(X, y, sample_weights, grid_shape) + active_mask = np.ones(setup.n_features_aug, dtype=bool) + if not fit_intercept: + # GramSetup always augments with the intercept column; drop it + # from the active set to mimic the legacy ``fit_intercept=False`` + # branch (which never augmented in the first place). + active_mask[-1] = False + return setup.solve(active_mask) From d753fa60840cd6bcebc1a4e988a8845b421a29c3 Mon Sep 17 00:00:00 2001 From: Gromwud Date: Wed, 20 May 2026 12:14:12 +0300 Subject: [PATCH 5/8] data loaders: ac / kdv / ns / vdp tweaks --- projects/pic/data/ac/ac.py | 12 ++++++------ projects/pic/data/kdv/kdv.py | 4 ++-- projects/pic/data/ns/ns.py | 6 +++--- projects/pic/data/vdp/vdp.py | 4 ++-- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/projects/pic/data/ac/ac.py b/projects/pic/data/ac/ac.py index aaad1e69..887c7fae 100644 --- a/projects/pic/data/ac/ac.py +++ b/projects/pic/data/ac/ac.py @@ -110,9 +110,9 @@ def AC_test(operator: CompoundOperator, foldername: str, noise_level: int = 0): print('Shapes:', data.shape, grid[0].shape) dimensionality = 1 - epde_search_obj = EpdeSearch(use_solver=False, use_pic=True, boundary=(5, 12), + epde_search_obj = EpdeSearch(use_solver=True, use_pic=True, boundary=(5, 12), coordinate_tensors=((grid[0], grid[1])), verbose_params={'show_iter_idx': True}, - device='cpu') + device='cuda') epde_search_obj.set_preprocessor(default_preprocessor_type='FD', preprocessor_kwargs={}) @@ -176,10 +176,10 @@ def ac_discovery(foldername, noise_level): print(f"CUDA version linked with PyTorch: {torch.version.cuda}") # Operator = fitness.SolverBasedFitness # Replace by the developed PIC-based operator. # Operator = fitness.PIC - #Operator = fitness.L2LRFitness + # Operator = fitness.L2LRFitness Operator = fitness.DeepXDEBasedFitness params = EvolutionaryParams() - #operator_params = params.get_default_params_for_operator('DiscrepancyBasedFitnessWithCV') #{"penalty_coeff": 0.2, "pinn_loss_mult": 1e4} + # operator_params = params.get_default_params_for_operator('DiscrepancyBasedFitnessWithCV') #{"penalty_coeff": 0.2, "pinn_loss_mult": 1e4} try: operator_params = params.get_default_params_for_operator('DeepXDEBasedFitness') except Exception as e: @@ -207,5 +207,5 @@ def ac_discovery(foldername, noise_level): directory = os.path.dirname(os.path.realpath(__file__)) ac_folder_name = os.path.join(directory) - #AC_test(fit_operator, ac_folder_name, 0) - ac_discovery(ac_folder_name, 0) + AC_test(fit_operator, ac_folder_name, 0) + # ac_discovery(ac_folder_name, 0) diff --git a/projects/pic/data/kdv/kdv.py b/projects/pic/data/kdv/kdv.py index f31e0dd1..04c3c962 100644 --- a/projects/pic/data/kdv/kdv.py +++ b/projects/pic/data/kdv/kdv.py @@ -482,11 +482,11 @@ def kdv_sindy_discovery(foldername, noise_level): directory = os.path.dirname(os.path.realpath(__file__)) kdv_folder_name = os.path.join(directory) - # KdV_test(fit_operator, kdv_folder_name, 0) + KdV_test(fit_operator, kdv_folder_name, 0) # KdV_h_test(fit_operator, kdv_folder_name, 0) # KdV_sga_test(fit_operator, kdv_folder_name, 0) # kdv_discovery(kdv_folder_name, 0) # kdv_h_discovery(kdv_folder_name, 0) # kdv_sga_discovery(kdv_folder_name, 5) - kdv_sindy_discovery(kdv_folder_name, 0) \ No newline at end of file + # kdv_sindy_discovery(kdv_folder_name, 0) \ No newline at end of file diff --git a/projects/pic/data/ns/ns.py b/projects/pic/data/ns/ns.py index 0bdfffff..fb5957ce 100644 --- a/projects/pic/data/ns/ns.py +++ b/projects/pic/data/ns/ns.py @@ -144,7 +144,7 @@ def ns_discovery(foldername, noise_level): # dimensionality = data.ndim - 1 - epde_search_obj = EpdeSearch(use_solver=True, multiobjective_mode=True, + epde_search_obj = EpdeSearch(use_solver=False, multiobjective_mode=True, use_pic=True, boundary=[21, 21, 46], coordinate_tensors=grid, device='cuda') @@ -152,10 +152,10 @@ def ns_discovery(foldername, noise_level): # preprocessor_kwargs={'epochs_max' : 1e3}) epde_search_obj.set_preprocessor(default_preprocessor_type='FD', preprocessor_kwargs={}) - popsize = 64 + popsize = 32 epde_search_obj.set_moeadd_params(population_size=popsize, - training_epochs=1) + training_epochs=5) custom_grid_tokens = CacheStoredTokens(token_type='grid', token_labels=['t', 'x'], diff --git a/projects/pic/data/vdp/vdp.py b/projects/pic/data/vdp/vdp.py index a98ef310..84484c0c 100644 --- a/projects/pic/data/vdp/vdp.py +++ b/projects/pic/data/vdp/vdp.py @@ -207,5 +207,5 @@ def vdp_discovery(foldername, noise_level): vdp_folder_name = os.path.join(directory) - VdP_test(fit_operator, vdp_folder_name, 0) - #vdp_discovery(vdp_folder_name, 0) \ No newline at end of file + # VdP_test(fit_operator, vdp_folder_name, 0) + vdp_discovery(vdp_folder_name, 0) \ No newline at end of file From 4cf50fea0b29c851e9f8ad65942466954ee2789c Mon Sep 17 00:00:00 2001 From: Gromwud Date: Wed, 20 May 2026 12:18:55 +0300 Subject: [PATCH 6/8] migrate thesis runners to projects/thesis/ with YAML configs Each per-system _thesis_run.py was a Python file mixing declarative state (name, truth equations, outdir, data_fun_pow, early_stop_on_truth) with imperative state (load_data, build_extra_tokens). Split into: projects/thesis/configs/.yaml (declarative, 14 systems) projects/thesis/adapters/.py (load_data + optional build_extra_tokens, 14 systems) projects/thesis/thesis_runner.py (gains load_config(yaml) and a run_smoke `outdir=` kwarg that lands tagged sweeps under results///) projects/thesis/run.py ("python run.py lv --reps 30 --outdir test_v2") projects/thesis/run_ablation.py (same CLI, defaults --pipelines to the 6 off- diagonal cells of the 2x2x2) projects/thesis/thesis_metrics.py (unchanged, copied) projects/thesis/thesis_aggregate.py (new --root flag; glob updated to results/*/*.json) projects/thesis/thesis_ablation_aggregate.py (same; recognises all 8 cells) burgers_sln_100.csv added because the new burgers_inviscid adapter references it; otherwise a fresh clone breaks. .gitignore: per-rep result JSONs under projects/thesis/results/ and the two aggregator summary JSONs are regenerated outputs -- gitignored so fresh clones don't inherit ~half a million lines of historical run data. Existing JSONs stay on disk; aggregators run against them as before. The 18 legacy _thesis_run.py / _ablation_run.py scripts and the 4 shared modules at projects/pic/data/ root were never tracked in git, so this commit introduces only additions. --- .gitignore | 5 + projects/pic/data/burgers/burgers_sln_100.csv | 101 ++++ projects/thesis/__init__.py | 0 projects/thesis/adapters/__init__.py | 0 projects/thesis/adapters/ac.py | 16 + projects/thesis/adapters/burgers_inviscid.py | 18 + projects/thesis/adapters/burgers_viscous.py | 18 + projects/thesis/adapters/kdv.py | 18 + projects/thesis/adapters/kdv_cossin.py | 35 ++ projects/thesis/adapters/ks.py | 18 + projects/thesis/adapters/lorenz.py | 14 + projects/thesis/adapters/lv.py | 14 + projects/thesis/adapters/ns.py | 13 + projects/thesis/adapters/ode.py | 21 + projects/thesis/adapters/pde_compound.py | 17 + projects/thesis/adapters/pde_divide.py | 17 + projects/thesis/adapters/vdp.py | 26 + projects/thesis/adapters/wave.py | 17 + projects/thesis/configs/ac.yaml | 5 + projects/thesis/configs/burgers_inviscid.yaml | 6 + projects/thesis/configs/burgers_viscous.yaml | 6 + projects/thesis/configs/kdv.yaml | 6 + projects/thesis/configs/kdv_cossin.yaml | 6 + projects/thesis/configs/ks.yaml | 6 + projects/thesis/configs/lorenz.yaml | 9 + projects/thesis/configs/lv.yaml | 6 + projects/thesis/configs/ns.yaml | 7 + projects/thesis/configs/ode.yaml | 6 + projects/thesis/configs/pde_compound.yaml | 6 + projects/thesis/configs/pde_divide.yaml | 6 + projects/thesis/configs/vdp.yaml | 6 + projects/thesis/configs/wave.yaml | 6 + projects/thesis/run.py | 92 +++ projects/thesis/run_ablation.py | 32 ++ projects/thesis/thesis_ablation_aggregate.py | 221 ++++++++ projects/thesis/thesis_aggregate.py | 147 +++++ projects/thesis/thesis_metrics.py | 221 ++++++++ projects/thesis/thesis_runner.py | 522 ++++++++++++++++++ 38 files changed, 1690 insertions(+) create mode 100644 projects/pic/data/burgers/burgers_sln_100.csv create mode 100644 projects/thesis/__init__.py create mode 100644 projects/thesis/adapters/__init__.py create mode 100644 projects/thesis/adapters/ac.py create mode 100644 projects/thesis/adapters/burgers_inviscid.py create mode 100644 projects/thesis/adapters/burgers_viscous.py create mode 100644 projects/thesis/adapters/kdv.py create mode 100644 projects/thesis/adapters/kdv_cossin.py create mode 100644 projects/thesis/adapters/ks.py create mode 100644 projects/thesis/adapters/lorenz.py create mode 100644 projects/thesis/adapters/lv.py create mode 100644 projects/thesis/adapters/ns.py create mode 100644 projects/thesis/adapters/ode.py create mode 100644 projects/thesis/adapters/pde_compound.py create mode 100644 projects/thesis/adapters/pde_divide.py create mode 100644 projects/thesis/adapters/vdp.py create mode 100644 projects/thesis/adapters/wave.py create mode 100644 projects/thesis/configs/ac.yaml create mode 100644 projects/thesis/configs/burgers_inviscid.yaml create mode 100644 projects/thesis/configs/burgers_viscous.yaml create mode 100644 projects/thesis/configs/kdv.yaml create mode 100644 projects/thesis/configs/kdv_cossin.yaml create mode 100644 projects/thesis/configs/ks.yaml create mode 100644 projects/thesis/configs/lorenz.yaml create mode 100644 projects/thesis/configs/lv.yaml create mode 100644 projects/thesis/configs/ns.yaml create mode 100644 projects/thesis/configs/ode.yaml create mode 100644 projects/thesis/configs/pde_compound.yaml create mode 100644 projects/thesis/configs/pde_divide.yaml create mode 100644 projects/thesis/configs/vdp.yaml create mode 100644 projects/thesis/configs/wave.yaml create mode 100644 projects/thesis/run.py create mode 100644 projects/thesis/run_ablation.py create mode 100644 projects/thesis/thesis_ablation_aggregate.py create mode 100644 projects/thesis/thesis_aggregate.py create mode 100644 projects/thesis/thesis_metrics.py create mode 100644 projects/thesis/thesis_runner.py diff --git a/.gitignore b/.gitignore index 9904af17..e5395b97 100644 --- a/.gitignore +++ b/.gitignore @@ -129,3 +129,8 @@ dmypy.json .pyre/ #cache /cache/*.tar + +# Thesis run outputs (regenerated by projects/thesis/run.py + aggregators) +projects/thesis/results/ +projects/thesis/thesis_summary.json +projects/thesis/thesis_ablation_summary.json diff --git a/projects/pic/data/burgers/burgers_sln_100.csv b/projects/pic/data/burgers/burgers_sln_100.csv new file mode 100644 index 00000000..c5f6b9da --- /dev/null +++ b/projects/pic/data/burgers/burgers_sln_100.csv @@ -0,0 +1,101 @@ +500.,502.5125628140703,505.0505050505051,507.6142131979695,510.2040816326531,512.8205128205129,515.4639175257732,518.1347150259068,520.8333333333334,523.5602094240837,526.3157894736842,529.1005291005292,531.9148936170212,534.7593582887699,537.6344086021506,540.5405405405404,543.4782608695651,546.4480874316939,549.4505494505494,552.4861878453039,555.5555555555555,558.659217877095,561.7977528089888,564.9717514124294,568.1818181818182,571.4285714285714,574.7126436781609,578.0346820809249,581.3953488372093,584.7953216374269,588.2352941176471,591.7159763313609,595.2380952380952,598.8023952095809,602.4096385542169,606.0606060606061,609.7560975609756,613.4969325153374,617.283950617284,621.1180124223603,625.,628.930817610063,632.9113924050632,636.9426751592356,641.025641025641,645.1612903225806,649.3506493506493,653.5947712418301,657.8947368421053,662.2516556291391,666.6666666666666,671.1409395973154,675.6756756756756,680.2721088435374,684.931506849315,689.6551724137931,694.4444444444445,699.3006993006994,704.2253521126761,709.2198581560283,714.2857142857143,719.4244604316547,724.6376811594204,729.92700729927,735.2941176470589,740.7407407407406,746.2686567164179,751.8796992481202,757.5757575757577,763.3587786259542,769.2307692307694,775.1937984496124,781.25,787.4015748031495,793.6507936507936,800.,806.4516129032259,813.0081300813008,819.672131147541,826.4462809917355,833.3333333333334,840.3361344537815,847.4576271186442,854.7008547008547,862.0689655172413,869.5652173913045,877.1929824561403,884.9557522123894,892.8571428571428,900.900900900901,909.090909090909,917.4311926605507,925.9259259259259,934.5794392523366,943.3962264150942,952.3809523809525,961.5384615384614,970.873786407767,980.3921568627451,990.09900990099,1000. +495.,497.4874371859296,500.00000000000006,502.5380710659898,505.10204081632656,507.69230769230774,510.3092783505155,512.9533678756477,515.625,518.3246073298429,521.0526315789473,523.8095238095239,526.595744680851,529.4117647058823,532.2580645161291,535.1351351351351,538.0434782608695,540.983606557377,543.9560439560439,546.9613259668508,550.,553.072625698324,556.1797752808989,559.3220338983051,562.5,565.7142857142857,568.9655172413793,572.2543352601157,575.5813953488372,578.9473684210526,582.3529411764706,585.7988165680474,589.2857142857143,592.814371257485,596.3855421686748,600.,603.6585365853658,607.361963190184,611.1111111111111,614.9068322981367,618.75,622.6415094339624,626.5822784810126,630.5732484076433,634.6153846153845,638.7096774193549,642.8571428571429,647.0588235294118,651.3157894736843,655.6291390728477,660.,664.4295302013423,668.9189189189188,673.469387755102,678.0821917808219,682.7586206896552,687.5,692.3076923076924,697.1830985915493,702.127659574468,707.1428571428571,712.230215827338,717.3913043478262,722.6277372262774,727.9411764705883,733.3333333333333,738.8059701492538,744.3609022556391,750.0000000000001,755.7251908396946,761.5384615384617,767.4418604651162,773.4375,779.527559055118,785.7142857142857,792.,798.3870967741937,804.8780487804878,811.4754098360656,818.1818181818182,825.,831.9327731092437,838.9830508474577,846.1538461538462,853.448275862069,860.8695652173914,868.4210526315788,876.1061946902655,883.9285714285713,891.891891891892,900.,908.2568807339451,916.6666666666665,925.2336448598132,933.9622641509433,942.857142857143,951.9230769230768,961.1650485436893,970.5882352941176,980.1980198019802,990. +490.,492.4623115577889,494.949494949495,497.46192893401013,500.,502.56410256410265,505.1546391752578,507.77202072538864,510.4166666666667,513.0890052356021,515.7894736842105,518.5185185185186,521.2765957446809,524.0641711229946,526.8817204301076,529.7297297297297,532.6086956521739,535.51912568306,538.4615384615385,541.4364640883978,544.4444444444445,547.4860335195531,550.5617977528091,553.6723163841808,556.8181818181819,560.,563.2183908045977,566.4739884393064,569.7674418604652,573.0994152046783,576.4705882352941,579.8816568047338,583.3333333333334,586.8263473053893,590.3614457831326,593.939393939394,597.560975609756,601.2269938650306,604.9382716049382,608.6956521739131,612.5,616.3522012578617,620.2531645569619,624.2038216560509,628.2051282051282,632.258064516129,636.3636363636364,640.5228758169935,644.7368421052632,649.0066225165564,653.3333333333333,657.7181208053692,662.1621621621622,666.6666666666666,671.2328767123287,675.8620689655173,680.5555555555555,685.3146853146854,690.1408450704226,695.0354609929077,700.,705.0359712230215,710.144927536232,715.3284671532847,720.5882352941177,725.9259259259259,731.3432835820896,736.8421052631578,742.4242424242425,748.091603053435,753.846153846154,759.6899224806201,765.625,771.6535433070866,777.7777777777777,784.,790.3225806451613,796.7479674796748,803.2786885245902,809.9173553719008,816.6666666666667,823.5294117647059,830.5084745762713,837.6068376068376,844.8275862068965,852.1739130434784,859.6491228070175,867.2566371681417,874.9999999999999,882.882882882883,890.9090909090909,899.0825688073396,907.4074074074073,915.8878504672898,924.5283018867924,933.3333333333335,942.3076923076923,951.4563106796116,960.7843137254902,970.2970297029702,980. +485.,487.4371859296482,489.89898989898995,492.38578680203045,494.8979591836735,497.4358974358975,500.00000000000006,502.5906735751296,505.20833333333337,507.85340314136124,510.52631578947364,513.2275132275133,515.9574468085107,518.7165775401069,521.5053763440861,524.3243243243243,527.1739130434783,530.0546448087431,532.9670329670329,535.9116022099448,538.8888888888889,541.8994413407821,544.9438202247192,548.0225988700565,551.1363636363637,554.2857142857142,557.471264367816,560.6936416184972,563.953488372093,567.2514619883041,570.5882352941177,573.9644970414201,577.3809523809524,580.8383233532935,584.3373493975904,587.8787878787879,591.4634146341464,595.0920245398773,598.7654320987654,602.4844720496895,606.25,610.0628930817611,613.9240506329113,617.8343949044586,621.7948717948717,625.8064516129032,629.8701298701299,633.9869281045752,638.1578947368422,642.384105960265,646.6666666666666,651.006711409396,655.4054054054054,659.8639455782313,664.3835616438356,668.9655172413793,673.6111111111111,678.3216783216784,683.0985915492959,687.9432624113474,692.8571428571429,697.841726618705,702.8985507246377,708.029197080292,713.2352941176471,718.5185185185185,723.8805970149255,729.3233082706766,734.848484848485,740.4580152671755,746.1538461538463,751.9379844961239,757.8125,763.779527559055,769.8412698412698,776.,782.2580645161291,788.6178861788618,795.0819672131148,801.6528925619835,808.3333333333334,815.1260504201681,822.0338983050848,829.0598290598291,836.2068965517241,843.4782608695652,850.8771929824561,858.4070796460178,866.0714285714286,873.873873873874,881.8181818181818,889.9082568807341,898.148148148148,906.5420560747665,915.0943396226414,923.809523809524,932.6923076923076,941.747572815534,950.9803921568626,960.3960396039604,970. +480.,482.4120603015075,484.8484848484849,487.3096446700507,489.7959183673469,492.3076923076924,494.84536082474233,497.4093264248705,500.00000000000006,502.6178010471204,505.2631578947368,507.936507936508,510.63829787234044,513.3689839572191,516.1290322580646,518.9189189189188,521.7391304347826,524.5901639344262,527.4725274725274,530.3867403314918,533.3333333333334,536.3128491620112,539.3258426966293,542.3728813559322,545.4545454545455,548.5714285714286,551.7241379310344,554.9132947976879,558.139534883721,561.4035087719298,564.7058823529412,568.0473372781065,571.4285714285714,574.8502994011976,578.3132530120482,581.8181818181819,585.3658536585366,588.9570552147239,592.5925925925926,596.2732919254659,600.,603.7735849056604,607.5949367088607,611.4649681528662,615.3846153846154,619.3548387096774,623.3766233766233,627.4509803921569,631.578947368421,635.7615894039735,640.,644.2953020134229,648.6486486486486,653.0612244897959,657.5342465753424,662.0689655172414,666.6666666666666,671.3286713286714,676.0563380281691,680.8510638297871,685.7142857142858,690.6474820143885,695.6521739130436,700.7299270072992,705.8823529411766,711.1111111111111,716.4179104477612,721.8045112781955,727.2727272727274,732.824427480916,738.4615384615386,744.1860465116279,750.,755.9055118110235,761.9047619047618,768.,774.1935483870968,780.4878048780488,786.8852459016393,793.3884297520661,800.,806.7226890756302,813.5593220338984,820.5128205128206,827.5862068965516,834.7826086956522,842.1052631578947,849.5575221238938,857.1428571428571,864.864864864865,872.7272727272727,880.7339449541286,888.8888888888888,897.1962616822431,905.6603773584905,914.2857142857144,923.076923076923,932.0388349514564,941.1764705882352,950.4950495049504,960. +475.,477.3869346733668,479.79797979797985,482.23350253807104,484.6938775510204,487.17948717948724,489.6907216494846,492.22797927461147,494.7916666666667,497.3821989528796,500.,502.64550264550275,505.3191489361702,508.0213903743315,510.75268817204307,513.5135135135134,516.3043478260869,519.1256830601093,521.978021978022,524.8618784530387,527.7777777777778,530.7262569832402,533.7078651685393,536.723163841808,539.7727272727274,542.8571428571428,545.9770114942528,549.1329479768787,552.3255813953489,555.5555555555555,558.8235294117648,562.1301775147929,565.4761904761905,568.8622754491018,572.289156626506,575.7575757575758,579.2682926829268,582.8220858895705,586.4197530864197,590.0621118012423,593.75,597.4842767295598,601.2658227848101,605.0955414012739,608.9743589743589,612.9032258064516,616.8831168831168,620.9150326797386,625.,629.1390728476822,633.3333333333333,637.5838926174497,641.8918918918919,646.2585034013605,650.6849315068492,655.1724137931035,659.7222222222222,664.3356643356644,669.0140845070423,673.7588652482268,678.5714285714286,683.4532374100719,688.4057971014494,693.4306569343065,698.529411764706,703.7037037037037,708.955223880597,714.2857142857142,719.6969696969697,725.1908396946565,730.769230769231,736.4341085271317,742.1875,748.0314960629921,753.968253968254,760.,766.1290322580646,772.3577235772358,778.688524590164,785.1239669421487,791.6666666666667,798.3193277310925,805.0847457627119,811.965811965812,818.9655172413793,826.0869565217392,833.3333333333333,840.70796460177,848.2142857142857,855.855855855856,863.6363636363636,871.5596330275231,879.6296296296296,887.8504672897197,896.2264150943395,904.7619047619049,913.4615384615383,922.3300970873787,931.3725490196078,940.5940594059406,950. +470.,472.3618090452261,474.74747474747477,477.15736040609136,479.5918367346939,482.0512820512821,484.5360824742269,487.0466321243524,489.58333333333337,492.14659685863876,494.7368421052631,497.3544973544974,500.,502.67379679144375,505.37634408602156,508.108108108108,510.86956521739125,513.6612021857923,516.4835164835164,519.3370165745856,522.2222222222223,525.1396648044692,528.0898876404494,531.0734463276837,534.0909090909091,537.1428571428571,540.2298850574713,543.3526011560695,546.5116279069767,549.7076023391812,552.9411764705883,556.2130177514794,559.5238095238095,562.8742514970061,566.2650602409639,569.6969696969697,573.170731707317,576.6871165644172,580.2469135802469,583.8509316770187,587.5,591.1949685534591,594.9367088607594,598.7261146496814,602.5641025641025,606.4516129032257,610.3896103896104,614.3790849673203,618.421052631579,622.5165562913908,626.6666666666666,630.8724832214765,635.1351351351351,639.4557823129252,643.8356164383562,648.2758620689656,652.7777777777777,657.3426573426574,661.9718309859155,666.6666666666666,671.4285714285714,676.2589928057554,681.1594202898551,686.1313868613139,691.1764705882354,696.2962962962963,701.4925373134329,706.7669172932331,712.1212121212122,717.5572519083969,723.0769230769232,728.6821705426356,734.375,740.1574803149606,746.031746031746,752.,758.0645161290323,764.2276422764228,770.4918032786885,776.8595041322315,783.3333333333334,789.9159663865546,796.6101694915255,803.4188034188035,810.3448275862069,817.3913043478261,824.5614035087718,831.8584070796461,839.2857142857142,846.8468468468469,854.5454545454545,862.3853211009176,870.3703703703703,878.5046728971963,886.7924528301886,895.2380952380954,903.8461538461538,912.6213592233009,921.5686274509803,930.6930693069307,940. +465.,467.3366834170854,469.69696969696975,472.0812182741116,474.48979591836735,476.92307692307696,479.38144329896915,481.8652849740933,484.37500000000006,486.9109947643979,489.4736842105263,492.06349206349216,494.6808510638298,497.3262032085561,500.00000000000006,502.70270270270265,505.4347826086956,508.19672131147536,510.9890109890109,513.8121546961327,516.6666666666667,519.5530726256983,522.4719101123596,525.4237288135594,528.409090909091,531.4285714285714,534.4827586206897,537.5722543352601,540.6976744186047,543.859649122807,547.0588235294118,550.2958579881657,553.5714285714286,556.8862275449102,560.2409638554217,563.6363636363636,567.0731707317073,570.5521472392638,574.074074074074,577.6397515527951,581.25,584.9056603773586,588.6075949367088,592.3566878980891,596.1538461538461,600.,603.8961038961039,607.843137254902,611.8421052631579,615.8940397350993,620.,624.1610738255034,628.3783783783783,632.6530612244898,636.986301369863,641.3793103448277,645.8333333333333,650.3496503496505,654.9295774647887,659.5744680851063,664.2857142857143,669.0647482014389,673.913043478261,678.8321167883212,683.8235294117648,688.8888888888888,694.0298507462687,699.2481203007518,704.5454545454546,709.9236641221373,715.3846153846155,720.9302325581394,726.5625,732.2834645669291,738.0952380952381,744.,750.,756.0975609756098,762.2950819672132,768.595041322314,775.,781.5126050420168,788.135593220339,794.8717948717949,801.7241379310344,808.6956521739131,815.7894736842105,823.0088495575221,830.3571428571428,837.8378378378379,845.4545454545454,853.211009174312,861.111111111111,869.158878504673,877.3584905660376,885.7142857142859,894.2307692307692,902.9126213592233,911.7647058823529,920.7920792079208,930. +460.,462.3115577889447,464.64646464646466,467.00507614213194,469.38775510204084,471.7948717948719,474.2268041237114,476.68393782383424,479.1666666666667,481.67539267015707,484.2105263157894,486.77248677248684,489.36170212765956,491.97860962566835,494.62365591397855,497.29729729729723,500.,502.7322404371584,505.49450549450546,508.2872928176796,511.11111111111114,513.9664804469273,516.8539325842697,519.7740112994351,522.7272727272727,525.7142857142857,528.735632183908,531.7919075144509,534.8837209302326,538.0116959064327,541.1764705882354,544.3786982248521,547.6190476190476,550.8982035928144,554.2168674698796,557.5757575757576,560.9756097560976,564.4171779141104,567.9012345679012,571.4285714285714,575.,578.6163522012579,582.2784810126582,585.9872611464967,589.7435897435897,593.5483870967741,597.4025974025974,601.3071895424837,605.2631578947369,609.271523178808,613.3333333333333,617.4496644295302,621.6216216216216,625.8503401360545,630.1369863013698,634.4827586206897,638.8888888888889,643.3566433566434,647.8873239436621,652.482269503546,657.1428571428571,661.8705035971223,666.6666666666667,671.5328467153284,676.4705882352941,681.4814814814814,686.5671641791046,691.7293233082706,696.9696969696971,702.2900763358778,707.6923076923078,713.1782945736434,718.75,724.4094488188975,730.1587301587301,736.,741.9354838709678,747.9674796747968,754.0983606557377,760.3305785123968,766.6666666666667,773.109243697479,779.6610169491526,786.3247863247864,793.103448275862,800.0000000000001,807.0175438596491,814.1592920353983,821.4285714285713,828.8288288288289,836.3636363636364,844.0366972477066,851.8518518518517,859.8130841121497,867.9245283018867,876.1904761904764,884.6153846153845,893.2038834951456,901.9607843137254,910.8910891089109,920. +455.,457.286432160804,459.59595959595964,461.92893401015226,464.2857142857143,466.66666666666674,469.07216494845363,471.5025906735752,473.95833333333337,476.4397905759162,478.9473684210526,481.4814814814816,484.04255319148933,486.6310160427807,489.24731182795705,491.8918918918918,494.5652173913043,497.2677595628415,499.99999999999994,502.76243093922653,505.5555555555556,508.3798882681564,511.2359550561798,514.1242937853108,517.0454545454546,520.,522.9885057471264,526.0115606936416,529.0697674418604,532.1637426900585,535.2941176470589,538.4615384615385,541.6666666666666,544.9101796407186,548.1927710843374,551.5151515151515,554.8780487804878,558.282208588957,561.7283950617284,565.2173913043479,568.75,572.3270440251573,575.9493670886076,579.6178343949044,583.3333333333333,587.0967741935484,590.9090909090909,594.7712418300654,598.6842105263158,602.6490066225166,606.6666666666666,610.738255033557,614.8648648648649,619.047619047619,623.2876712328767,627.5862068965517,631.9444444444445,636.3636363636365,640.8450704225353,645.3900709219857,650.,654.6762589928057,659.4202898550726,664.2335766423357,669.1176470588235,674.074074074074,679.1044776119403,684.2105263157895,689.3939393939395,694.6564885496183,700.0000000000001,705.4263565891472,710.9375,716.5354330708661,722.2222222222222,728.,733.8709677419355,739.8373983739838,745.9016393442623,752.0661157024794,758.3333333333334,764.7058823529412,771.1864406779662,777.7777777777778,784.4827586206897,791.304347826087,798.2456140350877,805.3097345132744,812.4999999999999,819.8198198198199,827.2727272727273,834.8623853211011,842.5925925925925,850.4672897196263,858.4905660377358,866.6666666666669,874.9999999999999,883.495145631068,892.156862745098,900.990099009901,910. +450.,452.2613065326633,454.54545454545456,456.8527918781726,459.18367346938777,461.5384615384616,463.9175257731959,466.3212435233161,468.75000000000006,471.2041884816754,473.6842105263158,476.19047619047626,478.72340425531917,481.28342245989296,483.87096774193554,486.4864864864864,489.1304347826087,491.80327868852453,494.5054945054944,497.2375690607735,500.,502.7932960893854,505.6179775280899,508.47457627118644,511.36363636363643,514.2857142857142,517.2413793103448,520.2312138728324,523.2558139534884,526.3157894736842,529.4117647058823,532.5443786982248,535.7142857142857,538.9221556886228,542.1686746987953,545.4545454545455,548.780487804878,552.1472392638037,555.5555555555555,559.0062111801243,562.5,566.0377358490567,569.6202531645569,573.248407643312,576.9230769230769,580.6451612903226,584.4155844155844,588.2352941176471,592.1052631578948,596.0264900662252,600.,604.026845637584,608.1081081081081,612.2448979591836,616.4383561643835,620.6896551724138,625.,629.3706293706294,633.8028169014085,638.2978723404254,642.8571428571429,647.4820143884892,652.1739130434784,656.9343065693431,661.764705882353,666.6666666666666,671.6417910447761,676.6917293233082,681.8181818181819,687.0229007633587,692.3076923076925,697.6744186046511,703.125,708.6614173228346,714.2857142857142,720.,725.8064516129033,731.7073170731708,737.7049180327868,743.8016528925621,750.,756.3025210084033,762.7118644067797,769.2307692307693,775.8620689655172,782.608695652174,789.4736842105262,796.4601769911504,803.5714285714286,810.810810810811,818.1818181818181,825.6880733944955,833.3333333333333,841.1214953271029,849.0566037735848,857.1428571428573,865.3846153846154,873.7864077669904,882.3529411764705,891.0891089108911,900. +445.,447.2361809045226,449.49494949494954,451.77664974619285,454.08163265306126,456.41025641025647,458.7628865979382,461.13989637305707,463.5416666666667,465.9685863874345,468.4210526315789,470.899470899471,473.40425531914894,475.9358288770053,478.49462365591404,481.081081081081,483.695652173913,486.3387978142076,489.01098901098896,491.71270718232046,494.44444444444446,497.2067039106145,500.00000000000006,502.82485875706215,505.68181818181824,508.57142857142856,511.4942528735632,514.4508670520231,517.4418604651163,520.46783625731,523.5294117647059,526.6272189349113,529.7619047619047,532.934131736527,536.1445783132531,539.3939393939394,542.6829268292682,546.0122699386503,549.3827160493827,552.7950310559007,556.25,559.748427672956,563.2911392405063,566.8789808917197,570.5128205128204,574.1935483870967,577.922077922078,581.6993464052288,585.5263157894738,589.4039735099338,593.3333333333333,597.3154362416108,601.3513513513514,605.4421768707483,609.5890410958904,613.7931034482759,618.0555555555555,622.3776223776224,626.7605633802817,631.2056737588651,635.7142857142858,640.2877697841726,644.9275362318841,649.6350364963504,654.4117647058824,659.2592592592592,664.179104477612,669.172932330827,674.2424242424244,679.3893129770992,684.6153846153848,689.9224806201549,695.3125,700.7874015748031,706.3492063492063,712.,717.741935483871,723.5772357723577,729.5081967213115,735.5371900826447,741.6666666666667,747.8991596638656,754.2372881355933,760.6837606837607,767.2413793103448,773.913043478261,780.7017543859648,787.6106194690266,794.6428571428571,801.801801801802,809.090909090909,816.51376146789,824.074074074074,831.7757009345795,839.6226415094338,847.6190476190478,855.7692307692307,864.0776699029126,872.5490196078431,881.1881188118812,890. +440.,442.2110552763819,444.44444444444446,446.70050761421317,448.9795918367347,451.2820512820513,453.60824742268045,455.95854922279796,458.33333333333337,460.73298429319374,463.1578947368421,465.6084656084657,468.0851063829787,470.58823529411757,473.11827956989254,475.6756756756756,478.2608695652174,480.87431693989066,483.51648351648345,486.18784530386745,488.8888888888889,491.62011173184356,494.38202247191015,497.17514124293785,500.00000000000006,502.85714285714283,505.74712643678157,508.6705202312139,511.6279069767442,514.6198830409356,517.6470588235294,520.7100591715977,523.8095238095239,526.9461077844312,530.1204819277109,533.3333333333334,536.5853658536586,539.8773006134969,543.2098765432098,546.583850931677,550.,553.4591194968555,556.9620253164557,560.5095541401273,564.1025641025641,567.741935483871,571.4285714285714,575.1633986928105,578.9473684210527,582.7814569536424,586.6666666666666,590.6040268456376,594.5945945945946,598.6394557823129,602.7397260273972,606.896551724138,611.1111111111111,615.3846153846155,619.7183098591549,624.1134751773048,628.5714285714286,633.0935251798561,637.68115942029,642.3357664233577,647.0588235294118,651.8518518518518,656.7164179104478,661.6541353383458,666.6666666666667,671.7557251908396,676.923076923077,682.1705426356589,687.5,692.9133858267716,698.4126984126983,704.,709.6774193548388,715.4471544715446,721.3114754098361,727.2727272727273,733.3333333333334,739.4957983193277,745.7627118644068,752.1367521367522,758.6206896551723,765.2173913043479,771.9298245614035,778.7610619469027,785.7142857142857,792.7927927927929,800.,807.3394495412846,814.8148148148147,822.4299065420562,830.188679245283,838.0952380952383,846.1538461538461,854.3689320388349,862.7450980392157,871.2871287128713,880. +435.,437.18592964824114,439.39393939393943,441.6243654822335,443.8775510204082,446.1538461538462,448.4536082474227,450.7772020725389,453.12500000000006,455.4973821989529,457.89473684210526,460.3174603174604,462.7659574468085,465.2406417112299,467.74193548387103,470.2702702702702,472.8260869565217,475.4098360655737,478.021978021978,480.6629834254144,483.33333333333337,486.0335195530726,488.76404494382024,491.52542372881356,494.31818181818187,497.1428571428571,499.99999999999994,502.89017341040466,505.81395348837214,508.77192982456137,511.764705882353,514.792899408284,517.8571428571429,520.9580838323354,524.0963855421687,527.2727272727273,530.4878048780488,533.7423312883436,537.037037037037,540.3726708074535,543.75,547.1698113207548,550.632911392405,554.140127388535,557.6923076923076,561.2903225806451,564.9350649350649,568.6274509803922,572.3684210526317,576.158940397351,580.,583.8926174496645,587.8378378378378,591.8367346938776,595.8904109589041,600.,604.1666666666666,608.3916083916084,612.6760563380283,617.0212765957446,621.4285714285714,625.8992805755396,630.4347826086957,635.0364963503649,639.7058823529412,644.4444444444445,649.2537313432837,654.1353383458646,659.0909090909092,664.1221374045801,669.2307692307694,674.4186046511627,679.6875,685.0393700787401,690.4761904761905,696.,701.6129032258065,707.3170731707316,713.1147540983607,719.00826446281,725.,731.09243697479,737.2881355932204,743.5897435897436,750.,756.5217391304349,763.1578947368421,769.9115044247789,776.7857142857142,783.7837837837839,790.9090909090909,798.165137614679,805.5555555555554,813.0841121495328,820.754716981132,828.5714285714287,836.5384615384614,844.6601941747573,852.9411764705882,861.3861386138614,870. +430.,432.16080402010044,434.34343434343435,436.5482233502538,438.7755102040816,441.0256410256411,443.298969072165,445.59585492227984,447.9166666666667,450.26178010471205,452.6315789473684,455.0264550264551,457.4468085106383,459.8930481283422,462.3655913978495,464.8648648648648,467.39130434782606,469.9453551912568,472.52747252747247,475.1381215469614,477.77777777777777,480.44692737430165,483.1460674157304,485.8757062146893,488.6363636363637,491.4285714285714,494.2528735632184,497.1098265895954,500.00000000000006,502.9239766081871,505.88235294117646,508.87573964497045,511.9047619047619,514.9700598802395,518.0722891566265,521.2121212121212,524.390243902439,527.6073619631902,530.8641975308642,534.1614906832299,537.5,540.8805031446542,544.3037974683543,547.7707006369426,551.2820512820513,554.8387096774194,558.4415584415584,562.0915032679738,565.7894736842105,569.5364238410597,573.3333333333333,577.1812080536913,581.081081081081,585.0340136054422,589.0410958904109,593.1034482758621,597.2222222222222,601.3986013986015,605.6338028169015,609.9290780141844,614.2857142857143,618.705035971223,623.1884057971015,627.7372262773723,632.3529411764706,637.037037037037,641.7910447761194,646.6165413533835,651.5151515151516,656.4885496183206,661.5384615384617,666.6666666666666,671.875,677.1653543307086,682.5396825396825,688.,693.5483870967743,699.1869918699186,704.9180327868853,710.7438016528926,716.6666666666667,722.6890756302521,728.8135593220339,735.0427350427351,741.3793103448276,747.8260869565219,754.3859649122807,761.0619469026549,767.8571428571428,774.7747747747749,781.8181818181818,788.9908256880735,796.2962962962962,803.7383177570094,811.320754716981,819.0476190476192,826.9230769230769,834.9514563106796,843.1372549019608,851.4851485148515,860. +425.,427.13567839195974,429.29292929292933,431.47208121827407,433.6734693877551,435.89743589743597,438.14432989690727,440.4145077720208,442.70833333333337,445.0261780104712,447.36842105263156,449.7354497354498,452.12765957446805,454.54545454545445,456.989247311828,459.45945945945937,461.95652173913044,464.48087431693983,467.03296703296695,469.6132596685083,472.22222222222223,474.8603351955307,477.5280898876405,480.225988700565,482.9545454545455,485.71428571428567,488.50574712643675,491.32947976878614,494.1860465116279,497.07602339181284,500.,502.9585798816568,505.95238095238096,508.9820359281438,512.0481927710844,515.1515151515151,518.2926829268292,521.4723926380368,524.6913580246913,527.9503105590063,531.25,534.5911949685535,537.9746835443037,541.4012738853503,544.8717948717948,548.3870967741935,551.9480519480519,555.5555555555555,559.2105263157895,562.9139072847682,566.6666666666666,570.4697986577181,574.3243243243243,578.2312925170068,582.1917808219177,586.2068965517242,590.2777777777777,594.4055944055945,598.5915492957747,602.8368794326241,607.1428571428571,611.5107913669065,615.9420289855074,620.4379562043796,625.,629.6296296296296,634.3283582089553,639.0977443609022,643.939393939394,648.854961832061,653.8461538461539,658.9147286821704,664.0625,669.2913385826771,674.6031746031746,680.,685.483870967742,691.0569105691056,696.7213114754098,702.4793388429753,708.3333333333334,714.2857142857143,720.3389830508476,726.4957264957266,732.7586206896551,739.1304347826087,745.6140350877192,752.212389380531,758.9285714285713,765.7657657657659,772.7272727272727,779.8165137614681,787.037037037037,794.3925233644861,801.8867924528302,809.5238095238096,817.3076923076923,825.242718446602,833.3333333333333,841.5841584158416,850. +420.,422.11055276381904,424.24242424242425,426.3959390862944,428.57142857142856,430.76923076923083,432.98969072164954,435.2331606217617,437.50000000000006,439.79057591623035,442.10526315789474,444.4444444444445,446.8085106382979,449.1978609625668,451.6129032258065,454.054054054054,456.52173913043475,459.0163934426229,461.5384615384615,464.0883977900553,466.6666666666667,469.27374301675974,471.91011235955057,474.5762711864407,477.2727272727273,480.,482.7586206896551,485.54913294797694,488.37209302325584,491.2280701754386,494.11764705882354,497.04142011834324,500.,502.99401197604794,506.02409638554224,509.0909090909091,512.1951219512194,515.3374233128834,518.5185185185185,521.7391304347826,525.,528.3018867924529,531.6455696202531,535.031847133758,538.4615384615385,541.9354838709677,545.4545454545455,549.0196078431372,552.6315789473684,556.2913907284768,560.,563.758389261745,567.5675675675676,571.4285714285714,575.3424657534247,579.3103448275863,583.3333333333333,587.4125874125875,591.5492957746479,595.7446808510638,600.,604.31654676259,608.6956521739131,613.1386861313869,617.6470588235295,622.2222222222222,626.8656716417911,631.578947368421,636.3636363636365,641.2213740458014,646.1538461538463,651.1627906976744,656.25,661.4173228346456,666.6666666666666,672.,677.4193548387098,682.9268292682926,688.5245901639345,694.2148760330579,700.,705.8823529411765,711.8644067796611,717.948717948718,724.1379310344827,730.4347826086957,736.8421052631578,743.3628318584072,750.,756.7567567567569,763.6363636363636,770.6422018348625,777.7777777777777,785.0467289719627,792.4528301886792,800.0000000000001,807.6923076923076,815.5339805825242,823.5294117647059,831.6831683168317,840. +415.,417.08542713567834,419.1919191919192,421.3197969543147,423.46938775510205,425.6410256410257,427.8350515463918,430.05181347150267,432.2916666666667,434.5549738219895,436.84210526315786,439.1534391534392,441.48936170212767,443.85026737967905,446.236559139785,448.6486486486486,451.0869565217391,453.55191256830597,456.043956043956,458.56353591160223,461.11111111111114,463.6871508379888,466.2921348314607,468.9265536723164,471.5909090909091,474.2857142857143,477.01149425287355,479.7687861271677,482.55813953488376,485.3801169590643,488.2352941176471,491.1242603550296,494.04761904761904,497.00598802395217,500.00000000000006,503.03030303030306,506.0975609756097,509.20245398773005,512.3456790123456,515.527950310559,518.75,522.0125786163522,525.3164556962025,528.6624203821656,532.051282051282,535.483870967742,538.961038961039,542.4836601307189,546.0526315789474,549.6688741721855,553.3333333333333,557.0469798657718,560.8108108108108,564.625850340136,568.4931506849315,572.4137931034484,576.3888888888889,580.4195804195805,584.5070422535211,588.6524822695035,592.8571428571429,597.1223021582733,601.449275362319,605.8394160583941,610.2941176470589,614.8148148148148,619.4029850746269,624.0601503759398,628.7878787878789,633.587786259542,638.4615384615386,643.4108527131782,648.4375,653.5433070866142,658.7301587301587,664.,669.3548387096774,674.7967479674796,680.327868852459,685.9504132231405,691.6666666666667,697.4789915966387,703.3898305084747,709.4017094017095,715.5172413793103,721.7391304347827,728.0701754385965,734.5132743362832,741.0714285714286,747.7477477477479,754.5454545454545,761.467889908257,768.5185185185185,775.7009345794394,783.0188679245282,790.4761904761906,798.076923076923,805.8252427184466,813.7254901960785,821.7821782178218,830. +410.,412.06030150753764,414.14141414141415,416.243654822335,418.36734693877554,420.51282051282055,422.6804123711341,424.87046632124355,427.08333333333337,429.3193717277487,431.57894736842104,433.86243386243393,436.17021276595744,438.5026737967914,440.8602150537635,443.24324324324317,445.65217391304344,448.087431693989,450.5494505494505,453.0386740331492,455.5555555555556,458.1005586592178,460.6741573033708,463.2768361581921,465.90909090909093,468.57142857142856,471.2643678160919,473.98843930635843,476.74418604651163,479.53216374269005,482.3529411764706,485.20710059171597,488.0952380952381,491.01796407185634,493.9759036144579,496.969696969697,500.,503.0674846625767,506.1728395061728,509.3167701863355,512.5,515.7232704402517,518.9873417721518,522.2929936305733,525.6410256410256,529.0322580645161,532.4675324675325,535.9477124183006,539.4736842105264,543.0463576158941,546.6666666666666,550.3355704697987,554.0540540540541,557.8231292517006,561.6438356164383,565.5172413793103,569.4444444444445,573.4265734265734,577.4647887323944,581.5602836879432,585.7142857142858,589.9280575539568,594.2028985507247,598.5401459854014,602.9411764705883,607.4074074074074,611.9402985074627,616.5413533834586,621.2121212121212,625.9541984732824,630.769230769231,635.6589147286821,640.625,645.6692913385826,650.7936507936507,656.,661.2903225806452,666.6666666666666,672.1311475409836,677.6859504132232,683.3333333333334,689.0756302521008,694.9152542372882,700.8547008547009,706.8965517241379,713.0434782608696,719.2982456140351,725.6637168141593,732.1428571428571,738.7387387387388,745.4545454545454,752.2935779816515,759.2592592592591,766.355140186916,773.5849056603773,780.9523809523811,788.4615384615383,796.1165048543689,803.9215686274509,811.8811881188119,820. +405.,407.03517587939695,409.0909090909091,411.1675126903553,413.265306122449,415.3846153846154,417.52577319587635,419.6891191709845,421.87500000000006,424.08376963350787,426.31578947368416,428.5714285714286,430.8510638297872,433.15508021390366,435.483870967742,437.83783783783775,440.2173913043478,442.6229508196721,445.054945054945,447.51381215469615,450.,452.5139664804469,455.05617977528095,457.6271186440678,460.22727272727275,462.85714285714283,465.5172413793103,468.2080924855492,470.93023255813955,473.6842105263158,476.47058823529414,479.2899408284024,482.1428571428571,485.0299401197605,487.9518072289157,490.90909090909093,493.9024390243902,496.9325153374233,500.,503.10559006211184,506.25,509.43396226415103,512.6582278481012,515.9235668789809,519.2307692307692,522.5806451612904,525.974025974026,529.4117647058823,532.8947368421053,536.4238410596026,540.,543.6241610738256,547.2972972972973,551.0204081632653,554.7945205479451,558.6206896551724,562.5,566.4335664335665,570.4225352112677,574.4680851063829,578.5714285714286,582.7338129496403,586.9565217391305,591.2408759124088,595.5882352941177,600.,604.4776119402985,609.0225563909775,613.6363636363637,618.3206106870228,623.0769230769232,627.906976744186,632.8125,637.7952755905511,642.8571428571428,648.,653.2258064516129,658.5365853658536,663.9344262295082,669.4214876033058,675.,680.6722689075631,686.4406779661017,692.3076923076924,698.2758620689655,704.3478260869566,710.5263157894736,716.8141592920355,723.2142857142857,729.7297297297298,736.3636363636364,743.119266055046,749.9999999999999,757.0093457943926,764.1509433962264,771.4285714285716,778.8461538461538,786.4077669902913,794.1176470588235,801.980198019802,810. +400.,402.01005025125625,404.04040404040404,406.0913705583756,408.16326530612247,410.25641025641033,412.3711340206186,414.50777202072544,416.6666666666667,418.848167539267,421.05263157894734,423.28042328042335,425.531914893617,427.807486631016,430.1075268817205,432.4324324324324,434.78260869565213,437.15846994535514,439.56043956043953,441.98895027624314,444.44444444444446,446.92737430167597,449.43820224719104,451.97740112994353,454.54545454545456,457.1428571428571,459.7701149425287,462.4277456647399,465.1162790697675,467.8362573099415,470.5882352941177,473.37278106508876,476.1904761904762,479.04191616766474,481.9277108433735,484.8484848484849,487.8048780487805,490.79754601226995,493.82716049382714,496.89440993788827,500.,503.1446540880504,506.3291139240506,509.5541401273885,512.8205128205128,516.1290322580645,519.4805194805194,522.875816993464,526.3157894736843,529.8013245033113,533.3333333333333,536.9127516778524,540.5405405405405,544.2176870748299,547.945205479452,551.7241379310345,555.5555555555555,559.4405594405595,563.3802816901409,567.3758865248226,571.4285714285714,575.5395683453237,579.7101449275364,583.9416058394161,588.2352941176471,592.5925925925926,597.0149253731344,601.5037593984962,606.0606060606061,610.6870229007633,615.3846153846155,620.1550387596899,625.,629.9212598425196,634.9206349206349,640.,645.1612903225807,650.4065040650406,655.7377049180328,661.1570247933885,666.6666666666667,672.2689075630252,677.9661016949153,683.7606837606838,689.655172413793,695.6521739130435,701.7543859649122,707.9646017699115,714.2857142857142,720.7207207207208,727.2727272727273,733.9449541284405,740.7407407407406,747.6635514018692,754.7169811320754,761.904761904762,769.2307692307692,776.6990291262136,784.313725490196,792.0792079207921,800. +395.,396.98492462311555,398.989898989899,401.01522842639594,403.0612244897959,405.1282051282052,407.21649484536084,409.3264248704664,411.45833333333337,413.6125654450262,415.7894736842105,417.98941798941803,420.2127659574468,422.45989304812827,424.731182795699,427.027027027027,429.3478260869565,431.69398907103823,434.065934065934,436.4640883977901,438.8888888888889,441.340782122905,443.82022471910113,446.32768361581924,448.86363636363643,451.4285714285714,454.0229885057471,456.64739884393066,459.3023255813954,461.98830409356725,464.7058823529412,467.4556213017752,470.23809523809524,473.0538922155689,475.90361445783134,478.7878787878788,481.7073170731707,484.6625766871166,487.6543209876543,490.68322981366464,493.75,496.85534591194977,499.99999999999994,503.18471337579615,506.41025641025635,509.6774193548387,512.987012987013,516.3398692810457,519.7368421052632,523.1788079470199,526.6666666666666,530.2013422818792,533.7837837837837,537.4149659863946,541.0958904109589,544.8275862068966,548.6111111111111,552.4475524475525,556.3380281690141,560.2836879432623,564.2857142857143,568.3453237410072,572.4637681159421,576.6423357664233,580.8823529411765,585.1851851851851,589.5522388059702,593.984962406015,598.4848484848486,603.0534351145037,607.6923076923078,612.4031007751937,617.1875,622.0472440944882,626.984126984127,632.,637.0967741935484,642.2764227642276,647.5409836065573,652.8925619834711,658.3333333333334,663.8655462184875,669.4915254237288,675.2136752136753,681.0344827586207,686.9565217391305,692.9824561403508,699.1150442477876,705.3571428571428,711.7117117117118,718.1818181818181,724.770642201835,731.4814814814814,738.3177570093459,745.2830188679244,752.3809523809525,759.6153846153845,766.9902912621359,774.5098039215686,782.1782178217821,790. +390.,391.95979899497485,393.93939393939394,395.9390862944162,397.9591836734694,400.00000000000006,402.0618556701031,404.1450777202073,406.25000000000006,408.37696335078533,410.52631578947364,412.69841269841277,414.8936170212766,417.1122994652406,419.3548387096775,421.62162162162156,423.9130434782609,426.22950819672127,428.5714285714285,430.93922651933707,433.33333333333337,435.75418994413405,438.2022471910113,440.67796610169495,443.18181818181824,445.71428571428567,448.27586206896547,450.8670520231214,453.48837209302326,456.14035087719293,458.8235294117647,461.53846153846155,464.2857142857143,467.0658682634731,469.8795180722892,472.72727272727275,475.609756097561,478.5276073619632,481.48148148148147,484.47204968944106,487.5,490.56603773584914,493.67088607594934,496.8152866242038,499.99999999999994,503.22580645161287,506.4935064935065,509.80392156862746,513.1578947368422,516.5562913907285,520.,523.4899328859061,527.027027027027,530.6122448979592,534.2465753424657,537.9310344827586,541.6666666666666,545.4545454545455,549.2957746478874,553.1914893617021,557.1428571428571,561.1510791366907,565.2173913043479,569.3430656934306,573.529411764706,577.7777777777777,582.089552238806,586.4661654135338,590.909090909091,595.4198473282443,600.0000000000001,604.6511627906976,609.375,614.1732283464567,619.047619047619,624.,629.0322580645162,634.1463414634146,639.344262295082,644.6280991735538,650.,655.4621848739496,661.0169491525425,666.6666666666667,672.4137931034483,678.2608695652175,684.2105263157895,690.2654867256638,696.4285714285713,702.7027027027028,709.0909090909091,715.5963302752294,722.2222222222222,728.9719626168226,735.8490566037735,742.857142857143,749.9999999999999,757.2815533980582,764.7058823529411,772.2772277227723,780. +385.,386.93467336683415,388.8888888888889,390.8629441624365,392.8571428571429,394.8717948717949,396.9072164948454,398.9637305699482,401.0416666666667,403.1413612565445,405.2631578947368,407.40740740740745,409.5744680851064,411.76470588235287,413.978494623656,416.21621621621614,418.4782608695652,420.7650273224043,423.07692307692304,425.414364640884,427.77777777777777,430.1675977653631,432.5842696629214,435.02824858757066,437.50000000000006,440.,442.5287356321839,445.08670520231215,447.6744186046512,450.29239766081866,452.94117647058823,455.62130177514797,458.3333333333333,461.0778443113773,463.85542168674704,466.6666666666667,469.5121951219512,472.39263803680984,475.3086419753086,478.26086956521743,481.25,484.2767295597485,487.3417721518987,490.44585987261144,493.58974358974353,496.7741935483871,500.,503.26797385620915,506.5789473684211,509.93377483443714,513.3333333333333,516.7785234899329,520.2702702702702,523.8095238095237,527.3972602739726,531.0344827586207,534.7222222222222,538.4615384615386,542.2535211267606,546.0992907801418,550.,553.956834532374,557.9710144927537,562.043795620438,566.1764705882354,570.3703703703703,574.6268656716418,578.9473684210526,583.3333333333334,587.7862595419847,592.3076923076924,596.8992248062015,601.5625,606.2992125984251,611.1111111111111,616.,620.9677419354839,626.0162601626016,631.1475409836065,636.3636363636364,641.6666666666667,647.0588235294118,652.542372881356,658.1196581196582,663.7931034482758,669.5652173913044,675.438596491228,681.4159292035398,687.5,693.6936936936938,700.,706.422018348624,712.9629629629629,719.6261682242991,726.4150943396226,733.3333333333335,740.3846153846154,747.5728155339806,754.9019607843137,762.3762376237623,770. +380.,381.90954773869345,383.8383838383839,385.78680203045684,387.7551020408163,389.7435897435898,391.75257731958766,393.78238341968915,395.83333333333337,397.90575916230364,400.,402.1164021164022,404.25531914893617,406.4171122994652,408.6021505376345,410.8108108108107,413.04347826086956,415.3005464480874,417.5824175824175,419.88950276243094,422.22222222222223,424.58100558659214,426.9662921348315,429.3785310734463,431.81818181818187,434.2857142857143,436.78160919540227,439.30635838150295,441.8604651162791,444.4444444444444,447.05882352941177,449.70414201183434,452.38095238095235,455.0898203592815,457.83132530120486,460.6060606060606,463.4146341463414,466.25766871165644,469.1358024691358,472.04968944099386,475.,477.98742138364787,481.012658227848,484.0764331210191,487.1794871794871,490.3225806451613,493.5064935064935,496.73202614379085,500.00000000000006,503.3112582781457,506.66666666666663,510.06711409395973,513.5135135135135,517.0068027210884,520.5479452054794,524.1379310344828,527.7777777777777,531.4685314685315,535.2112676056338,539.0070921985815,542.8571428571429,546.7625899280575,550.7246376811595,554.7445255474453,558.8235294117648,562.9629629629629,567.1641791044776,571.4285714285714,575.7575757575759,580.1526717557251,584.6153846153848,589.1472868217054,593.75,598.4251968503936,603.1746031746031,608.,612.9032258064517,617.8861788617886,622.9508196721312,628.099173553719,633.3333333333334,638.655462184874,644.0677966101696,649.5726495726497,655.1724137931034,660.8695652173914,666.6666666666666,672.566371681416,678.5714285714286,684.6846846846847,690.9090909090909,697.2477064220185,703.7037037037036,710.2803738317758,716.9811320754716,723.809523809524,730.7692307692307,737.8640776699029,745.0980392156863,752.4752475247525,760. +375.,376.88442211055275,378.7878787878788,380.71065989847716,382.6530612244898,384.61538461538464,386.59793814432993,388.6010362694301,390.625,392.67015706806285,394.7368421052631,396.82539682539687,398.93617021276594,401.0695187165775,403.225806451613,405.40540540540536,407.6086956521739,409.83606557377044,412.08791208791206,414.3646408839779,416.6666666666667,418.9944134078212,421.3483146067416,423.728813559322,426.1363636363637,428.57142857142856,431.03448275862064,433.5260115606937,436.046511627907,438.59649122807014,441.1764705882353,443.7869822485207,446.42857142857144,449.10179640718565,451.8072289156627,454.54545454545456,457.3170731707317,460.1226993865031,462.96296296296293,465.8385093167702,468.75,471.69811320754724,474.6835443037974,477.7070063694267,480.7692307692307,483.8709677419355,487.012987012987,490.19607843137254,493.42105263157896,496.68874172185434,500.,503.3557046979866,506.7567567567567,510.2040816326531,513.6986301369863,517.2413793103449,520.8333333333334,524.4755244755245,528.1690140845071,531.9148936170212,535.7142857142858,539.568345323741,543.4782608695652,547.4452554744526,551.4705882352941,555.5555555555555,559.7014925373135,563.9097744360902,568.1818181818182,572.5190839694656,576.923076923077,581.3953488372092,585.9375,590.5511811023622,595.2380952380952,600.,604.8387096774194,609.7560975609756,614.7540983606558,619.8347107438017,625.,630.2521008403362,635.5932203389831,641.0256410256411,646.551724137931,652.1739130434784,657.8947368421052,663.7168141592921,669.6428571428571,675.6756756756757,681.8181818181818,688.0733944954129,694.4444444444443,700.9345794392524,707.5471698113207,714.2857142857144,721.1538461538461,728.1553398058253,735.2941176470588,742.5742574257425,750. +370.,371.85929648241205,373.7373737373738,375.6345177664974,377.55102040816325,379.48717948717956,381.4432989690722,383.41968911917104,385.4166666666667,387.434554973822,389.4736842105263,391.5343915343916,393.6170212765957,395.72192513368975,397.8494623655915,399.99999999999994,402.17391304347825,404.37158469945354,406.59340659340654,408.83977900552486,411.11111111111114,413.4078212290502,415.7303370786517,418.07909604519773,420.4545454545455,422.85714285714283,425.28735632183907,427.74566473988443,430.2325581395349,432.74853801169587,435.29411764705884,437.8698224852071,440.4761904761905,443.1137724550899,445.7831325301205,448.4848484848485,451.2195121951219,453.9877300613497,456.79012345679007,459.62732919254665,462.5,465.4088050314466,468.35443037974676,471.33757961783436,474.3589743589743,477.41935483870964,480.5194805194805,483.66013071895424,486.8421052631579,490.06622516556297,493.3333333333333,496.64429530201346,500.,503.40136054421765,506.8493150684931,510.3448275862069,513.8888888888889,517.4825174825176,521.1267605633803,524.8226950354609,528.5714285714286,532.3741007194244,536.2318840579711,540.1459854014598,544.1176470588235,548.1481481481482,552.2388059701493,556.390977443609,560.6060606060607,564.8854961832061,569.2307692307694,573.6434108527131,578.125,582.6771653543307,587.3015873015872,592.,596.7741935483872,601.6260162601626,606.5573770491803,611.5702479338843,616.6666666666667,621.8487394957983,627.1186440677967,632.4786324786326,637.9310344827586,643.4782608695652,649.1228070175438,654.8672566371682,660.7142857142857,666.6666666666667,672.7272727272727,678.8990825688074,685.1851851851851,691.5887850467291,698.1132075471697,704.7619047619049,711.5384615384614,718.4466019417475,725.4901960784314,732.6732673267327,740. +365.,366.83417085427135,368.6868686868687,370.55837563451774,372.44897959183675,374.3589743589744,376.2886597938145,378.238341968912,380.20833333333337,382.19895287958116,384.2105263157895,386.2433862433863,388.2978723404255,390.3743315508021,392.47311827957,394.5945945945945,396.73913043478257,398.9071038251366,401.098901098901,403.31491712707185,405.5555555555556,407.8212290502793,410.11235955056185,412.42937853107344,414.7727272727273,417.1428571428571,419.54022988505744,421.9653179190752,424.4186046511628,426.9005847953216,429.4117647058824,431.9526627218935,434.5238095238095,437.12574850299404,439.7590361445784,442.42424242424244,445.1219512195122,447.85276073619633,450.61728395061726,453.416149068323,456.25,459.119496855346,462.02531645569616,464.968152866242,467.9487179487179,470.96774193548384,474.025974025974,477.12418300653593,480.2631578947369,483.44370860927154,486.66666666666663,489.93288590604027,493.2432432432432,496.5986394557823,500.,503.448275862069,506.9444444444444,510.48951048951056,514.0845070422536,517.7304964539006,521.4285714285714,525.1798561151079,528.9855072463769,532.8467153284671,536.7647058823529,540.7407407407408,544.7761194029852,548.8721804511277,553.0303030303031,557.2519083969465,561.5384615384617,565.891472868217,570.3125,574.8031496062991,579.3650793650793,584.,588.7096774193549,593.4959349593496,598.360655737705,603.305785123967,608.3333333333334,613.4453781512605,618.6440677966102,623.931623931624,629.3103448275862,634.7826086956522,640.3508771929825,646.0176991150443,651.7857142857142,657.6576576576578,663.6363636363636,669.724770642202,675.9259259259259,682.2429906542056,688.6792452830188,695.2380952380954,701.9230769230769,708.7378640776699,715.6862745098039,722.7722772277227,730. +360.,361.8090452261306,363.6363636363637,365.48223350253807,367.34693877551024,369.2307692307693,371.13402061855675,373.0569948186529,375.,376.9633507853403,378.9473684210526,380.952380952381,382.97872340425533,385.02673796791436,387.09677419354847,389.1891891891891,391.30434782608694,393.4426229508196,395.60439560439556,397.7900552486188,400.,402.23463687150837,404.49438202247194,406.77966101694915,409.0909090909091,411.4285714285714,413.7931034482758,416.1849710982659,418.60465116279073,421.05263157894734,423.5294117647059,426.0355029585799,428.57142857142856,431.13772455089827,433.7349397590362,436.3636363636364,439.0243902439024,441.717791411043,444.4444444444444,447.20496894409945,450.,452.83018867924534,455.6962025316455,458.59872611464965,461.5384615384615,464.51612903225805,467.53246753246754,470.5882352941177,473.68421052631584,476.82119205298017,480.,483.22147651006713,486.48648648648646,489.7959183673469,493.1506849315068,496.55172413793105,500.,503.4965034965036,507.0422535211268,510.6382978723404,514.2857142857143,517.9856115107914,521.7391304347827,525.5474452554745,529.4117647058824,533.3333333333333,537.3134328358209,541.3533834586466,545.4545454545455,549.618320610687,553.8461538461539,558.1395348837209,562.5,566.9291338582676,571.4285714285714,576.,580.6451612903227,585.3658536585366,590.1639344262295,595.0413223140496,600.,605.0420168067227,610.1694915254238,615.3846153846155,620.6896551724137,626.0869565217392,631.578947368421,637.1681415929204,642.8571428571428,648.6486486486488,654.5454545454545,660.5504587155964,666.6666666666666,672.8971962616823,679.2452830188679,685.7142857142858,692.3076923076923,699.0291262135922,705.8823529411765,712.8712871287129,720. +355.,356.7839195979899,358.5858585858586,360.40609137055833,362.2448979591837,364.10256410256414,365.979381443299,367.8756476683938,369.7916666666667,371.72774869109946,373.6842105263158,375.6613756613757,377.6595744680851,379.6791443850267,381.7204301075269,383.78378378378375,385.8695652173913,387.9781420765027,390.10989010989005,392.2651933701658,394.44444444444446,396.6480446927374,398.87640449438203,401.12994350282486,403.40909090909093,405.71428571428567,408.04597701149424,410.40462427745666,412.79069767441865,415.2046783625731,417.64705882352945,420.1183431952663,422.6190476190476,425.14970059880244,427.710843373494,430.3030303030303,432.9268292682927,435.5828220858896,438.2716049382716,440.9937888198758,443.75,446.5408805031447,449.3670886075949,452.2292993630573,455.1282051282051,458.06451612903226,461.038961038961,464.0522875816994,467.1052631578948,470.1986754966888,473.3333333333333,476.510067114094,479.72972972972974,482.99319727891157,486.3013698630137,489.65517241379314,493.05555555555554,496.50349650349654,500.00000000000006,503.5460992907801,507.14285714285717,510.7913669064748,514.4927536231885,518.2481751824818,522.0588235294118,525.9259259259259,529.8507462686567,533.8345864661654,537.878787878788,541.9847328244274,546.1538461538463,550.3875968992247,554.6875,559.0551181102362,563.4920634920635,568.,572.5806451612904,577.2357723577236,581.9672131147541,586.7768595041323,591.6666666666667,596.6386554621848,601.6949152542373,606.8376068376069,612.0689655172414,617.3913043478261,622.8070175438596,628.3185840707965,633.9285714285713,639.6396396396398,645.4545454545454,651.3761467889909,657.4074074074073,663.551401869159,669.8113207547169,676.1904761904763,682.6923076923076,689.3203883495146,696.078431372549,702.9702970297029,710. +350.,351.7587939698492,353.5353535353536,355.32994923857865,357.14285714285717,358.974358974359,360.8247422680413,362.69430051813475,364.58333333333337,366.4921465968586,368.4210526315789,370.37037037037044,372.3404255319149,374.33155080213896,376.3440860215054,378.37837837837833,380.4347826086956,382.51366120218574,384.6153846153846,386.7403314917127,388.8888888888889,391.06145251396646,393.2584269662922,395.48022598870057,397.72727272727275,400.,402.2988505747126,404.6242774566474,406.9767441860465,409.3567251461988,411.7647058823529,414.2011834319527,416.6666666666667,419.1616766467066,421.68674698795184,424.24242424242425,426.8292682926829,429.4478527607362,432.09876543209873,434.78260869565224,437.5,440.2515723270441,443.03797468354423,445.85987261146494,448.7179487179487,451.61290322580646,454.54545454545456,457.51633986928107,460.5263157894737,463.57615894039736,466.66666666666663,469.7986577181208,472.97297297297297,476.19047619047615,479.4520547945205,482.75862068965523,486.1111111111111,489.51048951048955,492.9577464788733,496.4539007092198,500.,503.59712230215825,507.2463768115943,510.94890510948903,514.7058823529412,518.5185185185185,522.3880597014926,526.3157894736842,530.3030303030304,534.3511450381679,538.4615384615386,542.6356589147287,546.875,551.1811023622047,555.5555555555555,560.,564.5161290322582,569.1056910569106,573.7704918032787,578.5123966942149,583.3333333333334,588.2352941176471,593.2203389830509,598.2905982905984,603.448275862069,608.6956521739131,614.0350877192982,619.4690265486726,625.,630.6306306306307,636.3636363636364,642.2018348623855,648.148148148148,654.2056074766356,660.3773584905659,666.6666666666667,673.076923076923,679.6116504854369,686.2745098039215,693.0693069306931,700. +345.,346.7336683417085,348.4848484848485,350.25380710659897,352.0408163265306,353.84615384615387,355.67010309278356,357.5129533678757,359.375,361.25654450261777,363.1578947368421,365.0793650793651,367.02127659574467,368.9839572192513,370.9677419354839,372.9729729729729,375.,377.04918032786884,379.12087912087907,381.2154696132597,383.33333333333337,385.4748603351955,387.64044943820227,389.8305084745763,392.04545454545456,394.2857142857143,396.551724137931,398.8439306358382,401.16279069767444,403.50877192982455,405.88235294117646,408.28402366863907,410.7142857142857,413.17365269461084,415.66265060240966,418.1818181818182,420.7317073170732,423.3128834355828,425.9259259259259,428.5714285714286,431.25,433.96226415094344,436.70886075949363,439.4904458598726,442.30769230769226,445.1612903225806,448.05194805194805,450.98039215686276,453.94736842105266,456.953642384106,460.,463.0872483221477,466.2162162162162,469.3877551020408,472.6027397260274,475.86206896551727,479.16666666666663,482.51748251748256,485.9154929577465,489.3617021276595,492.8571428571429,496.4028776978417,500.00000000000006,503.64963503649636,507.3529411764706,511.1111111111111,514.9253731343284,518.796992481203,522.7272727272727,526.7175572519084,530.7692307692308,534.8837209302325,539.0625,543.3070866141732,547.6190476190476,552.,556.4516129032259,560.9756097560976,565.5737704918033,570.2479338842975,575.,579.8319327731092,584.7457627118645,589.7435897435898,594.8275862068965,600.0000000000001,605.2631578947368,610.6194690265487,616.0714285714286,621.6216216216217,627.2727272727273,633.02752293578,638.8888888888888,644.8598130841123,650.9433962264151,657.1428571428572,663.4615384615385,669.9029126213592,676.4705882352941,683.1683168316831,690. +340.,341.7085427135678,343.4343434343435,345.1776649746193,346.9387755102041,348.7179487179488,350.51546391752584,352.33160621761664,354.1666666666667,356.020942408377,357.89473684210526,359.78835978835986,361.70212765957444,363.63636363636357,365.5913978494624,367.5675675675675,369.5652173913043,371.5846994535519,373.6263736263736,375.69060773480663,377.77777777777777,379.88826815642454,382.02247191011236,384.180790960452,386.3636363636364,388.57142857142856,390.8045977011494,393.06358381502895,395.34883720930236,397.6608187134503,400.,402.3668639053255,404.76190476190476,407.185628742515,409.63855421686753,412.1212121212121,414.6341463414634,417.17791411042947,419.75308641975306,422.36024844720504,425.,427.6729559748428,430.379746835443,433.1210191082802,435.89743589743586,438.7096774193548,441.55844155844153,444.44444444444446,447.3684210526316,450.3311258278146,453.3333333333333,456.37583892617454,459.4594594594594,462.5850340136054,465.7534246575342,468.96551724137936,472.22222222222223,475.5244755244756,478.87323943661977,482.26950354609926,485.7142857142857,489.2086330935252,492.75362318840587,496.35036496350364,500.00000000000006,503.7037037037037,507.4626865671642,511.2781954887218,515.1515151515152,519.0839694656488,523.0769230769232,527.1317829457364,531.25,535.4330708661416,539.6825396825396,544.,548.3870967741935,552.8455284552846,557.3770491803278,561.9834710743802,566.6666666666667,571.4285714285714,576.271186440678,581.1965811965813,586.2068965517241,591.304347826087,596.4912280701755,601.7699115044248,607.1428571428571,612.6126126126127,618.1818181818181,623.8532110091744,629.6296296296296,635.5140186915888,641.5094339622641,647.6190476190477,653.8461538461538,660.1941747572815,666.6666666666666,673.2673267326733,680. +335.,336.6834170854271,338.3838383838384,340.10152284263955,341.83673469387753,343.58974358974365,345.36082474226805,347.1502590673576,348.95833333333337,350.78534031413614,352.6315789473684,354.49735449735454,356.3829787234042,358.2887700534759,360.2150537634409,362.16216216216213,364.1304347826087,366.1202185792349,368.1318681318681,370.1657458563536,372.22222222222223,374.3016759776536,376.4044943820225,378.5310734463277,380.68181818181824,382.85714285714283,385.0574712643678,387.2832369942197,389.5348837209303,391.812865497076,394.11764705882354,396.44970414201185,398.8095238095238,401.1976047904192,403.61445783132535,406.06060606060606,408.5365853658536,411.04294478527606,413.5802469135802,416.1490683229814,418.75,421.3836477987422,424.05063291139237,426.75159235668787,429.48717948717945,432.258064516129,435.06493506493507,437.90849673202615,440.7894736842106,443.7086092715232,446.66666666666663,449.66442953020135,452.7027027027027,455.78231292517006,458.90410958904107,462.0689655172414,465.27777777777777,468.5314685314686,471.830985915493,475.17730496453896,478.57142857142856,482.0143884892086,485.5072463768117,489.05109489051097,492.64705882352945,496.29629629629625,500.00000000000006,503.7593984962406,507.5757575757576,511.4503816793893,515.3846153846155,519.3798449612402,523.4375,527.5590551181102,531.7460317460317,536.,540.3225806451613,544.7154471544716,549.1803278688525,553.7190082644628,558.3333333333334,563.0252100840336,567.7966101694916,572.6495726495727,577.5862068965517,582.608695652174,587.719298245614,592.9203539823009,598.2142857142857,603.6036036036037,609.0909090909091,614.6788990825689,620.3703703703703,626.1682242990655,632.0754716981131,638.0952380952382,644.2307692307692,650.4854368932039,656.8627450980392,663.3663366336633,670. +330.,331.6582914572864,333.33333333333337,335.0253807106599,336.734693877551,338.4615384615385,340.2061855670103,341.96891191709847,343.75,345.5497382198953,347.36842105263156,349.2063492063493,351.06382978723406,352.9411764705882,354.8387096774194,356.7567567567567,358.695652173913,360.655737704918,362.63736263736257,364.64088397790056,366.6666666666667,368.71508379888263,370.7865168539326,372.8813559322034,375.00000000000006,377.1428571428571,379.31034482758616,381.50289017341044,383.72093023255815,385.96491228070175,388.2352941176471,390.5325443786982,392.85714285714283,395.2095808383234,397.5903614457832,400.,402.4390243902439,404.9079754601227,407.4074074074074,409.93788819875783,412.5,415.09433962264154,417.7215189873417,420.3821656050955,423.07692307692304,425.80645161290323,428.57142857142856,431.37254901960785,434.2105263157895,437.0860927152318,440.,442.9530201342282,445.94594594594594,448.9795918367347,452.05479452054794,455.1724137931035,458.3333333333333,461.5384615384616,464.78873239436626,468.08510638297867,471.42857142857144,474.82014388489205,478.2608695652175,481.75182481751824,485.29411764705884,488.88888888888886,492.53731343283584,496.24060150375936,500.00000000000006,503.8167938931297,507.6923076923078,511.62790697674416,515.625,519.6850393700787,523.8095238095237,528.,532.258064516129,536.5853658536586,540.983606557377,545.4545454545455,550.,554.6218487394958,559.3220338983051,564.1025641025642,568.9655172413793,573.9130434782609,578.9473684210526,584.070796460177,589.2857142857142,594.5945945945947,600.,605.5045871559635,611.1111111111111,616.8224299065421,622.6415094339623,628.5714285714287,634.6153846153845,640.7766990291262,647.0588235294117,653.4653465346535,660. +325.,326.6331658291457,328.2828282828283,329.9492385786802,331.6326530612245,333.33333333333337,335.0515463917526,336.7875647668394,338.5416666666667,340.31413612565444,342.10526315789474,343.91534391534395,345.74468085106383,347.5935828877005,349.4623655913979,351.3513513513513,353.2608695652174,355.19125683060105,357.1428571428571,359.11602209944755,361.11111111111114,363.12849162011173,365.16853932584274,367.2316384180791,369.31818181818187,371.4285714285714,373.5632183908046,375.7225433526012,377.90697674418607,380.1169590643275,382.3529411764706,384.61538461538464,386.9047619047619,389.2215568862276,391.566265060241,393.93939393939394,396.3414634146341,398.7730061349693,401.2345679012345,403.7267080745342,406.25,408.8050314465409,411.3924050632911,414.01273885350315,416.66666666666663,419.3548387096774,422.0779220779221,424.83660130718954,427.63157894736844,430.46357615894044,433.3333333333333,436.2416107382551,439.18918918918916,442.1768707482993,445.20547945205476,448.2758620689655,451.38888888888886,454.5454545454546,457.7464788732395,460.99290780141837,464.2857142857143,467.62589928057554,471.01449275362324,474.45255474452557,477.9411764705883,481.48148148148147,485.0746268656717,488.72180451127815,492.4242424242425,496.1832061068702,500.0000000000001,503.87596899224803,507.8125,511.8110236220472,515.8730158730158,520.,524.1935483870968,528.4552845528456,532.7868852459017,537.1900826446281,541.6666666666667,546.218487394958,550.8474576271187,555.5555555555557,560.3448275862069,565.2173913043479,570.1754385964912,575.2212389380531,580.3571428571428,585.5855855855857,590.9090909090909,596.3302752293579,601.8518518518517,607.4766355140188,613.2075471698113,619.0476190476192,625.,631.0679611650486,637.2549019607843,643.5643564356435,650. +320.,321.608040201005,323.23232323232327,324.8730964467005,326.53061224489795,328.20512820512823,329.89690721649487,331.60621761658035,333.33333333333337,335.0785340314136,336.84210526315786,338.6243386243387,340.4255319148936,342.2459893048128,344.0860215053764,345.9459459459459,347.82608695652175,349.7267759562841,351.6483516483516,353.5911602209945,355.55555555555554,357.5418994413408,359.55056179775283,361.5819209039548,363.6363636363637,365.71428571428567,367.81609195402297,369.9421965317919,372.093023255814,374.26900584795317,376.47058823529414,378.698224852071,380.95238095238096,383.23353293413174,385.5421686746988,387.8787878787879,390.2439024390244,392.63803680981596,395.0617283950617,397.51552795031057,400.,402.5157232704403,405.06329113924045,407.6433121019108,410.2564102564102,412.9032258064516,415.5844155844156,418.30065359477123,421.0526315789474,423.841059602649,426.66666666666663,429.5302013422819,432.4324324324324,435.3741496598639,438.35616438356163,441.3793103448276,444.44444444444446,447.5524475524476,450.7042253521127,453.9007092198581,457.14285714285717,460.431654676259,463.76811594202906,467.15328467153284,470.5882352941177,474.074074074074,477.6119402985075,481.203007518797,484.84848484848493,488.54961832061065,492.3076923076924,496.1240310077519,500.,503.9370078740157,507.9365079365079,512.,516.1290322580645,520.3252032520325,524.5901639344263,528.9256198347108,533.3333333333334,537.8151260504202,542.3728813559322,547.0085470085471,551.7241379310344,556.5217391304349,561.4035087719298,566.3716814159293,571.4285714285713,576.5765765765766,581.8181818181818,587.1559633027524,592.5925925925925,598.1308411214955,603.7735849056603,609.5238095238096,615.3846153846154,621.3592233009708,627.4509803921568,633.6633663366337,640. +315.,316.5829145728643,318.1818181818182,319.7969543147208,321.42857142857144,323.0769230769231,324.74226804123714,326.4248704663213,328.125,329.84293193717275,331.57894736842104,333.33333333333337,335.1063829787234,336.89839572192506,338.7096774193549,340.54054054054046,342.39130434782606,344.2622950819672,346.15384615384613,348.0662983425415,350.,351.9553072625698,353.9325842696629,355.93220338983053,357.9545454545455,360.,362.06896551724134,364.16184971098266,366.27906976744185,368.4210526315789,370.5882352941177,372.78106508875743,375.,377.245508982036,379.51807228915663,381.8181818181818,384.1463414634146,386.5030674846626,388.88888888888886,391.304347826087,393.75,396.2264150943397,398.73417721518985,401.27388535031844,403.8461538461538,406.4516129032258,409.09090909090907,411.7647058823529,414.47368421052636,417.21854304635764,420.,422.81879194630875,425.6756756756757,428.57142857142856,431.50684931506845,434.4827586206897,437.5,440.5594405594406,443.661971830986,446.80851063829783,450.,453.2374100719424,456.52173913043487,459.8540145985401,463.2352941176471,466.66666666666663,470.1492537313433,473.6842105263158,477.27272727272737,480.9160305343511,484.6153846153847,488.3720930232558,492.1875,496.06299212598424,500.,504.,508.0645161290323,512.1951219512194,516.3934426229508,520.6611570247934,525.,529.4117647058823,533.8983050847459,538.4615384615385,543.103448275862,547.8260869565217,552.6315789473684,557.5221238938053,562.5,567.5675675675676,572.7272727272727,577.9816513761469,583.3333333333333,588.785046728972,594.3396226415093,600.0000000000001,605.7692307692307,611.6504854368932,617.6470588235294,623.7623762376238,630. +310.,311.5577889447236,313.13131313131316,314.7208121827411,316.3265306122449,317.948717948718,319.5876288659794,321.24352331606224,322.9166666666667,324.60732984293196,326.3157894736842,328.0423280423281,329.78723404255317,331.5508021390374,333.33333333333337,335.1351351351351,336.95652173913044,338.7978142076502,340.6593406593406,342.5414364640884,344.44444444444446,346.36871508379886,348.3146067415731,350.28248587570624,352.2727272727273,354.2857142857143,356.32183908045977,358.38150289017346,360.4651162790698,362.57309941520464,364.7058823529412,366.8639053254438,369.04761904761904,371.25748502994014,373.4939759036145,375.75757575757575,378.0487804878049,380.3680981595092,382.71604938271605,385.09316770186336,387.5,389.93710691823907,392.4050632911392,394.9044585987261,397.4358974358974,400.,402.5974025974026,405.2287581699346,407.8947368421053,410.59602649006627,413.3333333333333,416.1073825503356,418.9189189189189,421.7687074829932,424.6575342465753,427.58620689655174,430.55555555555554,433.5664335664336,436.6197183098592,439.71631205673754,442.8571428571429,446.0431654676259,449.2753623188406,452.55474452554745,455.8823529411765,459.25925925925924,462.6865671641791,466.16541353383457,469.69696969696975,473.28244274809157,476.923076923077,480.62015503875966,484.375,488.1889763779527,492.06349206349205,496.,500.00000000000006,504.0650406504065,508.1967213114754,512.396694214876,516.6666666666667,521.0084033613446,525.4237288135594,529.9145299145299,534.4827586206897,539.1304347826087,543.859649122807,548.6725663716815,553.5714285714286,558.5585585585586,563.6363636363636,568.8073394495414,574.074074074074,579.4392523364487,584.9056603773585,590.4761904761906,596.1538461538461,601.9417475728155,607.843137254902,613.8613861386139,620. +305.,306.5326633165829,308.0808080808081,309.6446700507614,311.2244897959184,312.8205128205129,314.4329896907217,316.0621761658031,317.70833333333337,319.3717277486911,321.05263157894734,322.7513227513228,324.468085106383,326.20320855614966,327.95698924731187,329.7297297297297,331.52173913043475,333.3333333333333,335.16483516483515,337.0165745856354,338.8888888888889,340.7821229050279,342.69662921348316,344.63276836158195,346.5909090909091,348.57142857142856,350.57471264367814,352.6011560693642,354.6511627906977,356.7251461988304,358.8235294117647,360.9467455621302,363.0952380952381,365.26946107784437,367.46987951807233,369.6969696969697,371.9512195121951,374.23312883435585,376.5432098765432,378.8819875776398,381.25,383.64779874213843,386.0759493670886,388.5350318471337,391.025641025641,393.5483870967742,396.1038961038961,398.6928104575164,401.3157894736842,403.9735099337749,406.66666666666663,409.3959731543624,412.16216216216213,414.96598639455783,417.80821917808214,420.68965517241384,423.6111111111111,426.5734265734266,429.5774647887324,432.62411347517724,435.7142857142857,438.84892086330933,442.02898550724643,445.2554744525547,448.5294117647059,451.85185185185185,455.22388059701495,458.64661654135335,462.1212121212122,465.648854961832,469.23076923076934,472.86821705426354,476.5625,480.31496062992125,484.1269841269841,488.,491.9354838709678,495.9349593495935,500.,504.1322314049587,508.33333333333337,512.6050420168067,516.9491525423729,521.3675213675214,525.8620689655172,530.4347826086957,535.0877192982456,539.8230088495576,544.6428571428571,549.5495495495496,554.5454545454545,559.6330275229359,564.8148148148148,570.0934579439253,575.4716981132075,580.9523809523811,586.5384615384614,592.2330097087379,598.0392156862745,603.960396039604,610. +300.,301.5075376884422,303.03030303030306,304.5685279187817,306.12244897959187,307.69230769230774,309.27835051546396,310.88082901554407,312.5,314.13612565445027,315.7894736842105,317.4603174603175,319.1489361702128,320.855614973262,322.58064516129036,324.32432432432427,326.0869565217391,327.86885245901635,329.67032967032964,331.49171270718233,333.33333333333337,335.19553072625695,337.0786516853933,338.98305084745766,340.90909090909093,342.85714285714283,344.8275862068965,346.82080924855495,348.8372093023256,350.8771929824561,352.94117647058823,355.0295857988166,357.14285714285717,359.28143712574854,361.44578313253015,363.6363636363636,365.8536585365854,368.09815950920245,370.3703703703703,372.67080745341616,375.,377.3584905660378,379.7468354430379,382.16560509554137,384.6153846153846,387.09677419354836,389.61038961038963,392.15686274509807,394.7368421052632,397.35099337748346,400.,402.6845637583893,405.4054054054054,408.1632653061224,410.958904109589,413.7931034482759,416.66666666666663,419.58041958041963,422.5352112676057,425.53191489361694,428.57142857142856,431.65467625899277,434.78260869565224,437.95620437956205,441.1764705882353,444.4444444444444,447.7611940298508,451.12781954887214,454.5454545454546,458.0152671755725,461.5384615384616,465.1162790697674,468.75,472.4409448818897,476.19047619047615,480.,483.87096774193554,487.8048780487805,491.8032786885246,495.86776859504135,500.,504.20168067226894,508.4745762711865,512.8205128205128,517.2413793103448,521.7391304347826,526.3157894736842,530.9734513274336,535.7142857142857,540.5405405405406,545.4545454545454,550.4587155963304,555.5555555555555,560.747663551402,566.0377358490565,571.4285714285716,576.9230769230769,582.5242718446602,588.2352941176471,594.059405940594,600. +295.,296.4824120603015,297.979797979798,299.492385786802,301.0204081632653,302.5641025641026,304.1237113402062,305.699481865285,307.2916666666667,308.9005235602094,310.52631578947364,312.1693121693122,313.82978723404256,315.50802139037427,317.20430107526886,318.91891891891885,320.65217391304344,322.4043715846994,324.1758241758241,325.9668508287293,327.77777777777777,329.608938547486,331.4606741573034,333.3333333333333,335.22727272727275,337.1428571428571,339.08045977011494,341.0404624277457,343.0232558139535,345.02923976608184,347.05882352941177,349.11242603550295,351.1904761904762,353.2934131736527,355.421686746988,357.57575757575756,359.7560975609756,361.9631901840491,364.1975308641975,366.4596273291926,368.75,371.06918238993717,373.4177215189873,375.796178343949,378.2051282051282,380.64516129032256,383.1168831168831,385.62091503267976,388.15789473684214,390.7284768211921,393.3333333333333,395.97315436241615,398.64864864864865,401.36054421768705,404.1095890410959,406.89655172413796,409.72222222222223,412.58741258741264,415.4929577464789,418.4397163120567,421.42857142857144,424.46043165467626,427.53623188405805,430.6569343065693,433.82352941176475,437.037037037037,440.2985074626866,443.6090225563909,446.96969696969705,450.3816793893129,453.8461538461539,457.3643410852713,460.9375,464.56692913385825,468.25396825396825,472.,475.8064516129033,479.6747967479675,483.60655737704917,487.603305785124,491.6666666666667,495.7983193277311,500.00000000000006,504.27350427350433,508.6206896551724,513.0434782608696,517.5438596491227,522.1238938053098,526.7857142857142,531.5315315315316,536.3636363636364,541.2844036697248,546.2962962962962,551.4018691588785,556.6037735849056,561.904761904762,567.3076923076923,572.8155339805826,578.4313725490196,584.1584158415842,590. +290.,291.4572864321608,292.92929292929296,294.4162436548223,295.9183673469388,297.43589743589746,298.9690721649485,300.51813471502595,302.08333333333337,303.6649214659686,305.2631578947368,306.87830687830694,308.51063829787233,310.1604278074866,311.82795698924735,313.5135135135135,315.2173913043478,316.9398907103825,318.68131868131866,320.44198895027625,322.22222222222223,324.0223463687151,325.8426966292135,327.683615819209,329.54545454545456,331.4285714285714,333.3333333333333,335.26011560693644,337.2093023255814,339.1812865497076,341.1764705882353,343.1952662721894,345.23809523809524,347.30538922155694,349.3975903614458,351.5151515151515,353.6585365853658,355.8282208588957,358.02469135802465,360.24844720496895,362.5,364.77987421383654,367.08860759493666,369.42675159235665,371.79487179487177,374.19354838709677,376.6233766233766,379.08496732026146,381.5789473684211,384.1059602649007,386.66666666666663,389.26174496644296,391.8918918918919,394.5578231292517,397.2602739726027,400.,402.77777777777777,405.59440559440566,408.4507042253521,411.3475177304964,414.2857142857143,417.2661870503597,420.2898550724638,423.35766423357666,426.47058823529414,429.6296296296296,432.83582089552243,436.09022556390977,439.39393939393943,442.7480916030534,446.15384615384625,449.61240310077517,453.125,456.6929133858267,460.3174603174603,464.,467.741935483871,471.5447154471545,475.40983606557376,479.3388429752066,483.33333333333337,487.3949579831933,491.5254237288136,495.7264957264958,499.99999999999994,504.34782608695656,508.77192982456137,513.2743362831859,517.8571428571428,522.5225225225226,527.2727272727273,532.1100917431194,537.037037037037,542.0560747663552,547.1698113207547,552.3809523809525,557.6923076923076,563.1067961165048,568.6274509803922,574.2574257425742,580. +285.,286.4321608040201,287.8787878787879,289.34010152284264,290.81632653061223,292.3076923076923,293.8144329896908,295.3367875647669,296.875,298.42931937172773,300.,301.5873015873016,303.1914893617021,304.8128342245989,306.45161290322585,308.10810810810807,309.78260869565213,311.4754098360655,313.18681318681314,314.91712707182324,316.6666666666667,318.43575418994413,320.22471910112364,322.03389830508473,323.8636363636364,325.7142857142857,327.5862068965517,329.4797687861272,331.3953488372093,333.3333333333333,335.29411764705884,337.27810650887574,339.2857142857143,341.3173652694611,343.37349397590367,345.45454545454544,347.5609756097561,349.69325153374234,351.85185185185185,354.0372670807454,356.25,358.4905660377359,360.75949367088606,363.0573248407643,365.38461538461536,367.741935483871,370.12987012987014,372.54901960784315,375.,377.4834437086093,380.,382.5503355704698,385.1351351351351,387.7551020408163,390.4109589041096,393.1034482758621,395.8333333333333,398.60139860139867,401.4084507042254,404.2553191489361,407.14285714285717,410.0719424460431,413.0434782608696,416.05839416058393,419.11764705882354,422.2222222222222,425.3731343283582,428.57142857142856,431.81818181818187,435.11450381679384,438.46153846153857,441.86046511627904,445.3125,448.81889763779526,452.38095238095235,456.,459.6774193548387,463.4146341463414,467.2131147540984,471.07438016528926,475.,478.9915966386555,483.0508474576272,487.17948717948724,491.37931034482756,495.65217391304355,500.,504.424778761062,508.9285714285714,513.5135135135135,518.1818181818181,522.9357798165139,527.7777777777777,532.7102803738318,537.7358490566037,542.857142857143,548.076923076923,553.3980582524272,558.8235294117646,564.3564356435644,570. +280.,281.4070351758794,282.82828282828285,284.2639593908629,285.7142857142857,287.17948717948724,288.65979381443304,290.1554404145078,291.6666666666667,293.1937172774869,294.7368421052631,296.29629629629636,297.8723404255319,299.4652406417112,301.07526881720435,302.70270270270265,304.3478260869565,306.0109289617486,307.6923076923077,309.3922651933702,311.11111111111114,312.8491620111732,314.60674157303373,316.38418079096044,318.1818181818182,320.,321.8390804597701,323.6994219653179,325.58139534883725,327.48538011695905,329.4117647058824,331.36094674556216,333.3333333333333,335.3293413173653,337.3493975903615,339.3939393939394,341.4634146341463,343.55828220858893,345.679012345679,347.82608695652175,350.,352.20125786163527,354.4303797468354,356.68789808917194,358.97435897435895,361.2903225806451,363.6363636363636,366.01307189542484,368.42105263157896,370.8609271523179,373.3333333333333,375.83892617449663,378.3783783783784,380.95238095238096,383.5616438356164,386.2068965517242,388.88888888888886,391.6083916083916,394.3661971830986,397.1631205673758,400.,402.8776978417266,405.79710144927543,408.75912408759126,411.764705882353,414.8148148148148,417.91044776119406,421.05263157894734,424.2424242424243,427.48091603053433,430.76923076923083,434.1085271317829,437.5,440.94488188976374,444.4444444444444,448.,451.61290322580646,455.2845528455284,459.016393442623,462.8099173553719,466.6666666666667,470.5882352941177,474.5762711864407,478.6324786324787,482.7586206896551,486.9565217391305,491.2280701754386,495.57522123893807,499.99999999999994,504.50450450450455,509.09090909090907,513.7614678899083,518.5185185185185,523.3644859813085,528.3018867924528,533.3333333333335,538.4615384615385,543.6893203883495,549.0196078431372,554.4554455445544,560. +275.,276.38190954773864,277.77777777777777,279.18781725888323,280.6122448979592,282.0512820512821,283.5051546391753,284.9740932642487,286.45833333333337,287.9581151832461,289.4736842105263,291.00529100529104,292.5531914893617,294.1176470588235,295.69892473118284,297.29729729729723,298.9130434782609,300.54644808743166,302.19780219780216,303.86740331491717,305.55555555555554,307.2625698324022,308.9887640449438,310.73446327683615,312.5,314.2857142857143,316.0919540229885,317.91907514450867,319.7674418604651,321.6374269005848,323.5294117647059,325.4437869822485,327.38095238095235,329.3413173652695,331.3253012048193,333.3333333333333,335.3658536585366,337.4233128834356,339.5061728395062,341.6149068322982,343.75,345.91194968553464,348.1012658227848,350.3184713375796,352.56410256410254,354.83870967741933,357.14285714285717,359.47712418300654,361.8421052631579,364.23841059602654,366.66666666666663,369.1275167785235,371.6216216216216,374.14965986394554,376.71232876712327,379.3103448275862,381.94444444444446,384.61538461538464,387.32394366197184,390.0709219858156,392.8571428571429,395.68345323741005,398.55072463768124,401.45985401459853,404.4117647058824,407.4074074074074,410.44776119402985,413.5338345864661,416.66666666666674,419.84732824427476,423.07692307692315,426.3565891472868,429.6875,433.07086614173227,436.5079365079365,440.,443.5483870967742,447.1544715447154,450.81967213114757,454.54545454545456,458.33333333333337,462.18487394957987,466.10169491525426,470.08547008547015,474.13793103448273,478.26086956521743,482.45614035087715,486.7256637168142,491.07142857142856,495.49549549549556,500.,504.58715596330285,509.2592592592592,514.0186915887851,518.8679245283018,523.8095238095239,528.8461538461538,533.9805825242719,539.2156862745098,544.5544554455446,550. +270.,271.35678391959794,272.72727272727275,274.11167512690355,275.51020408163265,276.92307692307696,278.35051546391753,279.79274611398966,281.25,282.72251308900525,284.2105263157895,285.7142857142858,287.2340425531915,288.7700534759358,290.32258064516134,291.8918918918919,293.4782608695652,295.0819672131147,296.70329670329664,298.3425414364641,300.,301.67597765363126,303.37078651685397,305.08474576271186,306.81818181818187,308.57142857142856,310.34482758620686,312.13872832369947,313.95348837209303,315.7894736842105,317.64705882352945,319.52662721893495,321.42857142857144,323.3532934131737,325.30120481927713,327.2727272727273,329.2682926829268,331.28834355828224,333.3333333333333,335.40372670807454,337.5,339.622641509434,341.77215189873414,343.9490445859872,346.15384615384613,348.38709677419354,350.64935064935065,352.94117647058823,355.2631578947369,357.6158940397351,360.,362.41610738255036,364.86486486486484,367.3469387755102,369.86301369863014,372.4137931034483,375.,377.62237762237766,380.2816901408451,382.9787234042553,385.7142857142857,388.4892086330935,391.304347826087,394.16058394160586,397.05882352941177,400.,402.9850746268657,406.0150375939849,409.0909090909091,412.21374045801525,415.3846153846155,418.6046511627907,421.875,425.19685039370074,428.57142857142856,432.,435.48387096774195,439.0243902439024,442.62295081967216,446.2809917355372,450.,453.781512605042,457.6271186440678,461.5384615384616,465.5172413793103,469.5652173913044,473.6842105263158,477.8761061946903,482.1428571428571,486.48648648648657,490.9090909090909,495.4128440366973,499.99999999999994,504.6728971962617,509.43396226415086,514.2857142857143,519.2307692307692,524.2718446601942,529.4117647058823,534.6534653465346,540. +265.,266.33165829145725,267.67676767676767,269.0355329949238,270.40816326530614,271.7948717948718,273.1958762886598,274.6113989637306,276.0416666666667,277.4869109947644,278.9473684210526,280.42328042328046,281.9148936170213,283.4224598930481,284.94623655913983,286.48648648648646,288.04347826086956,289.6174863387978,291.2087912087912,292.8176795580111,294.44444444444446,296.0893854748603,297.75280898876406,299.43502824858757,301.1363636363637,302.85714285714283,304.5977011494253,306.3583815028902,308.13953488372096,309.94152046783626,311.7647058823529,313.6094674556213,315.4761904761905,317.3652694610779,319.27710843373495,321.21212121212125,323.1707317073171,325.15337423312883,327.16049382716045,329.19254658385097,331.25,333.33333333333337,335.44303797468353,337.57961783439487,339.7435897435897,341.93548387096774,344.15584415584414,346.4052287581699,348.68421052631584,350.99337748344374,353.3333333333333,355.7046979865772,358.1081081081081,360.5442176870748,363.01369863013696,365.51724137931035,368.05555555555554,370.62937062937067,373.23943661971833,375.886524822695,378.57142857142856,381.294964028777,384.0579710144928,386.86131386861314,389.7058823529412,392.59259259259255,395.52238805970154,398.49624060150376,401.51515151515156,404.5801526717557,407.6923076923078,410.85271317829455,414.0625,417.3228346456693,420.6349206349206,424.,427.4193548387097,430.8943089430894,434.42622950819674,438.01652892561987,441.6666666666667,445.3781512605042,449.1525423728814,452.991452991453,456.8965517241379,460.86956521739137,464.91228070175436,469.0265486725664,473.21428571428567,477.4774774774775,481.8181818181818,486.23853211009185,490.7407407407407,495.3271028037384,499.99999999999994,504.7619047619049,509.6153846153846,514.5631067961165,519.6078431372549,524.7524752475248,530. +260.,261.30653266331655,262.62626262626264,263.95939086294413,265.3061224489796,266.6666666666667,268.0412371134021,269.43005181347155,270.83333333333337,272.25130890052355,273.6842105263158,275.1322751322752,276.59574468085106,278.07486631016036,279.56989247311833,281.08108108108104,282.6086956521739,284.1530054644808,285.71428571428567,287.292817679558,288.8888888888889,290.50279329608935,292.13483146067415,293.7853107344633,295.4545454545455,297.1428571428571,298.85057471264366,300.57803468208095,302.3255813953489,304.093567251462,305.88235294117646,307.6923076923077,309.5238095238095,311.37724550898207,313.2530120481928,315.1515151515152,317.0731707317073,319.0184049079755,320.98765432098764,322.98136645962734,325.,327.04402515723274,329.1139240506329,331.2101910828025,333.3333333333333,335.48387096774195,337.6623376623377,339.8692810457516,342.10526315789474,344.37086092715236,346.66666666666663,348.99328859060404,351.35135135135135,353.74149659863946,356.16438356164383,358.62068965517244,361.1111111111111,363.6363636363637,366.1971830985916,368.7943262411347,371.42857142857144,374.1007194244604,376.8115942028986,379.5620437956204,382.3529411764706,385.18518518518516,388.05970149253733,390.97744360902254,393.939393939394,396.9465648854962,400.00000000000006,403.1007751937984,406.25,409.44881889763775,412.69841269841265,416.,419.35483870967744,422.7642276422764,426.2295081967213,429.7520661157025,433.33333333333337,436.97478991596637,440.67796610169495,444.44444444444446,448.27586206896547,452.1739130434783,456.14035087719293,460.1769911504425,464.2857142857143,468.46846846846853,472.7272727272727,477.0642201834863,481.4814814814814,485.981308411215,490.566037735849,495.2380952380953,499.99999999999994,504.8543689320388,509.8039215686274,514.8514851485148,520. +255.,256.28140703517585,257.5757575757576,258.88324873096445,260.2040816326531,261.53846153846155,262.88659793814435,264.2487046632125,265.625,267.0157068062827,268.42105263157896,269.8412698412699,271.27659574468083,272.7272727272727,274.1935483870968,275.6756756756756,277.17391304347825,278.6885245901639,280.2197802197802,281.76795580110496,283.33333333333337,284.9162011173184,286.5168539325843,288.135593220339,289.7727272727273,291.4285714285714,293.10344827586204,294.7976878612717,296.51162790697674,298.2456140350877,300.,301.7751479289941,303.57142857142856,305.38922155688624,307.22891566265065,309.0909090909091,310.9756097560975,312.8834355828221,314.8148148148148,316.77018633540376,318.75,320.7547169811321,322.7848101265822,324.84076433121015,326.9230769230769,329.0322580645161,331.16883116883116,333.3333333333333,335.5263157894737,337.74834437086093,340.,342.2818791946309,344.5945945945946,346.9387755102041,349.31506849315065,351.72413793103453,354.16666666666663,356.6433566433567,359.1549295774648,361.70212765957444,364.2857142857143,366.90647482014384,369.56521739130443,372.26277372262774,375.,377.77777777777777,380.5970149253732,383.45864661654133,386.36363636363643,389.3129770992366,392.3076923076924,395.3488372093023,398.4375,401.5748031496063,404.76190476190476,408.,411.2903225806452,414.6341463414634,418.0327868852459,421.4876033057851,425.,428.57142857142856,432.2033898305085,435.8974358974359,439.6551724137931,443.47826086956525,447.36842105263156,451.3274336283186,455.35714285714283,459.45945945945954,463.6363636363636,467.8899082568808,472.2222222222222,476.63551401869165,481.13207547169804,485.7142857142858,490.38461538461536,495.1456310679612,500.,504.9504950495049,510. +250.,251.25628140703515,252.52525252525254,253.80710659898475,255.10204081632654,256.41025641025647,257.7319587628866,259.0673575129534,260.4166666666667,261.78010471204186,263.1578947368421,264.5502645502646,265.9574468085106,267.37967914438497,268.8172043010753,270.2702702702702,271.73913043478257,273.22404371584696,274.7252747252747,276.24309392265195,277.77777777777777,279.3296089385475,280.8988764044944,282.4858757062147,284.0909090909091,285.7142857142857,287.35632183908046,289.01734104046244,290.69767441860466,292.39766081871346,294.11764705882354,295.85798816568047,297.6190476190476,299.40119760479047,301.20481927710847,303.03030303030306,304.8780487804878,306.7484662576687,308.641975308642,310.55900621118013,312.5,314.4654088050315,316.4556962025316,318.4713375796178,320.5128205128205,322.5806451612903,324.67532467532465,326.79738562091507,328.94736842105266,331.12582781456956,333.3333333333333,335.5704697986577,337.8378378378378,340.1360544217687,342.4657534246575,344.82758620689657,347.22222222222223,349.6503496503497,352.11267605633805,354.60992907801415,357.14285714285717,359.71223021582733,362.3188405797102,364.963503649635,367.64705882352945,370.3703703703703,373.13432835820896,375.9398496240601,378.78787878787887,381.6793893129771,384.6153846153847,387.5968992248062,390.625,393.70078740157476,396.8253968253968,400.,403.2258064516129,406.5040650406504,409.8360655737705,413.22314049586777,416.6666666666667,420.16806722689074,423.7288135593221,427.35042735042737,431.03448275862064,434.78260869565224,438.59649122807014,442.4778761061947,446.4285714285714,450.4504504504505,454.5454545454545,458.71559633027533,462.96296296296293,467.2897196261683,471.6981132075471,476.19047619047626,480.7692307692307,485.4368932038835,490.19607843137254,495.049504950495,500. +245.,246.23115577889445,247.4747474747475,248.73096446700507,250.,251.28205128205133,252.5773195876289,253.88601036269432,255.20833333333334,256.5445026178011,257.89473684210526,259.2592592592593,260.63829787234044,262.0320855614973,263.4408602150538,264.86486486486484,266.30434782608694,267.75956284153,269.2307692307692,270.7182320441989,272.22222222222223,273.74301675977654,275.28089887640454,276.8361581920904,278.40909090909093,280.,281.60919540229884,283.2369942196532,284.8837209302326,286.54970760233914,288.2352941176471,289.9408284023669,291.6666666666667,293.41317365269464,295.1807228915663,296.969696969697,298.780487804878,300.6134969325153,302.4691358024691,304.34782608695656,306.25,308.17610062893084,310.12658227848095,312.10191082802544,314.1025641025641,316.1290322580645,318.1818181818182,320.26143790849676,322.3684210526316,324.5033112582782,326.66666666666663,328.8590604026846,331.0810810810811,333.3333333333333,335.61643835616434,337.93103448275866,340.27777777777777,342.6573426573427,345.0704225352113,347.51773049645385,350.,352.51798561151077,355.072463768116,357.66423357664235,360.29411764705884,362.96296296296293,365.6716417910448,368.4210526315789,371.21212121212125,374.0458015267175,376.923076923077,379.84496124031006,382.8125,385.8267716535433,388.88888888888886,392.,395.16129032258067,398.3739837398374,401.6393442622951,404.9586776859504,408.33333333333337,411.7647058823529,415.25423728813564,418.8034188034188,422.41379310344826,426.0869565217392,429.82456140350877,433.6283185840708,437.49999999999994,441.4414414414415,445.45454545454544,449.5412844036698,453.70370370370364,457.9439252336449,462.2641509433962,466.66666666666674,471.15384615384613,475.7281553398058,480.3921568627451,485.1485148514851,490. +240.,241.20603015075375,242.42424242424244,243.65482233502536,244.89795918367346,246.1538461538462,247.42268041237116,248.70466321243526,250.00000000000003,251.3089005235602,252.6315789473684,253.968253968254,255.31914893617022,256.68449197860957,258.0645161290323,259.4594594594594,260.8695652173913,262.2950819672131,263.7362637362637,265.1933701657459,266.6666666666667,268.1564245810056,269.6629213483146,271.1864406779661,272.72727272727275,274.2857142857143,275.8620689655172,277.4566473988439,279.0697674418605,280.7017543859649,282.3529411764706,284.02366863905326,285.7142857142857,287.4251497005988,289.1566265060241,290.90909090909093,292.6829268292683,294.47852760736197,296.2962962962963,298.1366459627329,300.,301.8867924528302,303.79746835443035,305.7324840764331,307.6923076923077,309.6774193548387,311.68831168831167,313.72549019607845,315.7894736842105,317.88079470198676,320.,322.14765100671144,324.3243243243243,326.53061224489795,328.7671232876712,331.0344827586207,333.3333333333333,335.6643356643357,338.02816901408454,340.42553191489355,342.8571428571429,345.32374100719426,347.8260869565218,350.3649635036496,352.9411764705883,355.55555555555554,358.2089552238806,360.90225563909775,363.6363636363637,366.412213740458,369.2307692307693,372.09302325581393,375.,377.95275590551176,380.9523809523809,384.,387.0967741935484,390.2439024390244,393.44262295081967,396.6942148760331,400.,403.3613445378151,406.7796610169492,410.2564102564103,413.7931034482758,417.3913043478261,421.05263157894734,424.7787610619469,428.57142857142856,432.4324324324325,436.3636363636364,440.3669724770643,444.4444444444444,448.59813084112153,452.8301886792452,457.1428571428572,461.5384615384615,466.0194174757282,470.5882352941176,475.2475247524752,480. +235.,236.18090452261305,237.37373737373738,238.57868020304568,239.79591836734696,241.02564102564105,242.26804123711344,243.5233160621762,244.79166666666669,246.07329842931938,247.36842105263156,248.6772486772487,250.,251.33689839572187,252.68817204301078,254.054054054054,255.43478260869563,256.83060109289613,258.2417582417582,259.6685082872928,261.11111111111114,262.5698324022346,264.0449438202247,265.5367231638418,267.04545454545456,268.57142857142856,270.11494252873564,271.6763005780347,273.25581395348837,274.8538011695906,276.47058823529414,278.1065088757397,279.76190476190476,281.43712574850304,283.13253012048193,284.8484848484849,286.5853658536585,288.3435582822086,290.12345679012344,291.92546583850935,293.75,295.5974842767296,297.4683544303797,299.3630573248407,301.28205128205127,303.22580645161287,305.1948051948052,307.18954248366015,309.2105263157895,311.2582781456954,313.3333333333333,315.43624161073825,317.56756756756755,319.7278911564626,321.9178082191781,324.1379310344828,326.38888888888886,328.6713286713287,330.98591549295776,333.3333333333333,335.7142857142857,338.1294964028777,340.57971014492756,343.06569343065695,345.5882352941177,348.14814814814815,350.74626865671644,353.38345864661653,356.0606060606061,358.77862595419845,361.5384615384616,364.3410852713178,367.1875,370.0787401574803,373.015873015873,376.,379.03225806451616,382.1138211382114,385.24590163934425,388.4297520661157,391.6666666666667,394.9579831932773,398.30508474576277,401.70940170940173,405.17241379310343,408.69565217391306,412.2807017543859,415.92920353982305,419.6428571428571,423.42342342342346,427.27272727272725,431.1926605504588,435.18518518518516,439.25233644859816,443.3962264150943,447.6190476190477,451.9230769230769,456.31067961165047,460.78431372549016,465.34653465346537,470. +230.,231.15577889447235,232.32323232323233,233.50253807106597,234.69387755102042,235.89743589743594,237.1134020618557,238.34196891191712,239.58333333333334,240.83769633507853,242.1052631578947,243.38624338624342,244.68085106382978,245.98930481283418,247.31182795698928,248.64864864864862,250.,251.3661202185792,252.74725274725273,254.1436464088398,255.55555555555557,256.98324022346367,258.42696629213486,259.88700564971754,261.3636363636364,262.85714285714283,264.367816091954,265.89595375722547,267.4418604651163,269.00584795321635,270.5882352941177,272.18934911242604,273.8095238095238,275.4491017964072,277.1084337349398,278.7878787878788,280.4878048780488,282.2085889570552,283.9506172839506,285.7142857142857,287.5,289.30817610062894,291.1392405063291,292.99363057324837,294.87179487179486,296.7741935483871,298.7012987012987,300.65359477124184,302.63157894736844,304.635761589404,306.66666666666663,308.7248322147651,310.8108108108108,312.9251700680272,315.0684931506849,317.2413793103448,319.44444444444446,321.6783216783217,323.94366197183103,326.241134751773,328.57142857142856,330.9352517985611,333.33333333333337,335.7664233576642,338.2352941176471,340.7407407407407,343.2835820895523,345.8646616541353,348.48484848484856,351.1450381679389,353.8461538461539,356.5891472868217,359.375,362.2047244094488,365.07936507936506,368.,370.9677419354839,373.9837398373984,377.04918032786884,380.1652892561984,383.33333333333337,386.5546218487395,389.8305084745763,393.1623931623932,396.551724137931,400.00000000000006,403.50877192982455,407.07964601769913,410.71428571428567,414.41441441441447,418.1818181818182,422.0183486238533,425.92592592592587,429.90654205607484,433.9622641509433,438.0952380952382,442.30769230769226,446.6019417475728,450.9803921568627,455.44554455445547,460. +225.,226.13065326633165,227.27272727272728,228.4263959390863,229.59183673469389,230.7692307692308,231.95876288659795,233.16062176165806,234.37500000000003,235.6020942408377,236.8421052631579,238.09523809523813,239.36170212765958,240.64171122994648,241.93548387096777,243.2432432432432,244.56521739130434,245.90163934426226,247.2527472527472,248.61878453038676,250.,251.3966480446927,252.80898876404495,254.23728813559322,255.68181818181822,257.1428571428571,258.6206896551724,260.1156069364162,261.6279069767442,263.1578947368421,264.70588235294116,266.2721893491124,267.85714285714283,269.4610778443114,271.08433734939763,272.72727272727275,274.390243902439,276.07361963190186,277.77777777777777,279.50310559006215,281.25,283.01886792452837,284.81012658227843,286.624203821656,288.46153846153845,290.3225806451613,292.2077922077922,294.11764705882354,296.0526315789474,298.0132450331126,300.,302.013422818792,304.05405405405406,306.1224489795918,308.2191780821918,310.3448275862069,312.5,314.6853146853147,316.90140845070425,319.1489361702127,321.42857142857144,323.7410071942446,326.0869565217392,328.46715328467155,330.8823529411765,333.3333333333333,335.82089552238807,338.3458646616541,340.90909090909093,343.51145038167937,346.15384615384625,348.83720930232556,351.5625,354.3307086614173,357.1428571428571,360.,362.90322580645164,365.8536585365854,368.8524590163934,371.90082644628103,375.,378.1512605042017,381.35593220338984,384.61538461538464,387.9310344827586,391.304347826087,394.7368421052631,398.2300884955752,401.7857142857143,405.4054054054055,409.09090909090907,412.84403669724776,416.66666666666663,420.56074766355147,424.5283018867924,428.57142857142867,432.6923076923077,436.8932038834952,441.17647058823525,445.54455445544556,450. +220.,221.10552763819095,222.22222222222223,223.35025380710658,224.48979591836735,225.64102564102566,226.80412371134022,227.97927461139898,229.16666666666669,230.36649214659687,231.57894736842104,232.80423280423284,234.04255319148936,235.29411764705878,236.55913978494627,237.8378378378378,239.1304347826087,240.43715846994533,241.75824175824172,243.09392265193372,244.44444444444446,245.81005586592178,247.19101123595507,248.58757062146893,250.00000000000003,251.42857142857142,252.87356321839079,254.33526011560696,255.8139534883721,257.3099415204678,258.8235294117647,260.35502958579883,261.9047619047619,263.4730538922156,265.06024096385545,266.6666666666667,268.2926829268293,269.93865030674846,271.6049382716049,273.2919254658385,275.,276.72955974842773,278.4810126582278,280.25477707006365,282.05128205128204,283.8709677419355,285.7142857142857,287.58169934640523,289.47368421052636,291.3907284768212,293.3333333333333,295.3020134228188,297.2972972972973,299.31972789115645,301.3698630136986,303.448275862069,305.55555555555554,307.69230769230774,309.85915492957747,312.0567375886524,314.2857142857143,316.54676258992805,318.840579710145,321.1678832116788,323.5294117647059,325.9259259259259,328.3582089552239,330.8270676691729,333.33333333333337,335.8778625954198,338.4615384615385,341.08527131782944,343.75,346.4566929133858,349.20634920634916,352.,354.8387096774194,357.7235772357723,360.65573770491807,363.6363636363636,366.6666666666667,369.74789915966386,372.8813559322034,376.0683760683761,379.31034482758616,382.60869565217394,385.96491228070175,389.38053097345136,392.85714285714283,396.3963963963964,400.,403.6697247706423,407.40740740740733,411.2149532710281,415.0943396226415,419.04761904761915,423.07692307692304,427.18446601941747,431.37254901960785,435.64356435643566,440. +215.,216.08040201005022,217.17171717171718,218.2741116751269,219.3877551020408,220.51282051282055,221.6494845360825,222.79792746113992,223.95833333333334,225.13089005235602,226.3157894736842,227.51322751322755,228.72340425531914,229.9465240641711,231.18279569892476,232.4324324324324,233.69565217391303,234.9726775956284,236.26373626373623,237.5690607734807,238.88888888888889,240.22346368715083,241.5730337078652,242.93785310734464,244.31818181818184,245.7142857142857,247.1264367816092,248.5549132947977,250.00000000000003,251.46198830409355,252.94117647058823,254.43786982248523,255.95238095238096,257.4850299401198,259.03614457831327,260.6060606060606,262.1951219512195,263.8036809815951,265.4320987654321,267.08074534161494,268.75,270.4402515723271,272.15189873417717,273.8853503184713,275.64102564102564,277.4193548387097,279.2207792207792,281.0457516339869,282.89473684210526,284.76821192052984,286.66666666666663,288.59060402684565,290.5405405405405,292.5170068027211,294.52054794520546,296.55172413793105,298.6111111111111,300.69930069930075,302.81690140845075,304.9645390070922,307.14285714285717,309.3525179856115,311.59420289855075,313.86861313868616,316.1764705882353,318.5185185185185,320.8955223880597,323.30827067669173,325.7575757575758,328.2442748091603,330.76923076923083,333.3333333333333,335.9375,338.5826771653543,341.26984126984127,344.,346.77419354838713,349.5934959349593,352.45901639344265,355.3719008264463,358.33333333333337,361.34453781512605,364.40677966101697,367.52136752136755,370.6896551724138,373.91304347826093,377.1929824561403,380.53097345132744,383.9285714285714,387.38738738738743,390.9090909090909,394.49541284403676,398.1481481481481,401.8691588785047,405.6603773584905,409.5238095238096,413.46153846153845,417.4757281553398,421.5686274509804,425.74257425742576,430. +210.,211.05527638190952,212.12121212121212,213.1979695431472,214.28571428571428,215.38461538461542,216.49484536082477,217.61658031088086,218.75000000000003,219.89528795811518,221.05263157894737,222.22222222222226,223.40425531914894,224.5989304812834,225.80645161290326,227.027027027027,228.26086956521738,229.50819672131146,230.76923076923075,232.04419889502765,233.33333333333334,234.63687150837987,235.95505617977528,237.28813559322035,238.63636363636365,240.,241.37931034482756,242.77456647398847,244.18604651162792,245.6140350877193,247.05882352941177,248.52071005917162,250.,251.49700598802397,253.01204819277112,254.54545454545456,256.0975609756097,257.6687116564417,259.25925925925924,260.8695652173913,262.5,264.15094339622647,265.82278481012656,267.515923566879,269.2307692307692,270.96774193548384,272.72727272727275,274.5098039215686,276.3157894736842,278.1456953642384,280.,281.8791946308725,283.7837837837838,285.7142857142857,287.67123287671234,289.65517241379314,291.66666666666663,293.70629370629376,295.77464788732397,297.8723404255319,300.,302.158273381295,304.34782608695656,306.56934306569343,308.82352941176475,311.1111111111111,313.43283582089555,315.7894736842105,318.18181818181824,320.6106870229007,323.07692307692315,325.5813953488372,328.125,330.7086614173228,333.3333333333333,336.,338.7096774193549,341.4634146341463,344.26229508196724,347.10743801652893,350.,352.94117647058823,355.93220338983053,358.974358974359,362.06896551724134,365.21739130434787,368.4210526315789,371.6814159292036,375.,378.37837837837844,381.8181818181818,385.32110091743124,388.88888888888886,392.52336448598135,396.2264150943396,400.00000000000006,403.8461538461538,407.7669902912621,411.7647058823529,415.84158415841586,420. +205.,206.03015075376882,207.07070707070707,208.1218274111675,209.18367346938777,210.25641025641028,211.34020618556704,212.43523316062178,213.54166666666669,214.65968586387436,215.78947368421052,216.93121693121697,218.08510638297872,219.2513368983957,220.43010752688176,221.62162162162159,222.82608695652172,224.0437158469945,225.27472527472526,226.5193370165746,227.7777777777778,229.0502793296089,230.3370786516854,231.63841807909606,232.95454545454547,234.28571428571428,235.63218390804596,236.99421965317921,238.37209302325581,239.76608187134502,241.1764705882353,242.60355029585799,244.04761904761904,245.50898203592817,246.98795180722894,248.4848484848485,250.,251.53374233128835,253.0864197530864,254.65838509316774,256.25,257.86163522012583,259.4936708860759,261.14649681528664,262.8205128205128,264.51612903225805,266.23376623376623,267.9738562091503,269.7368421052632,271.52317880794703,273.3333333333333,275.1677852348993,277.02702702702703,278.9115646258503,280.82191780821915,282.7586206896552,284.72222222222223,286.7132867132867,288.7323943661972,290.7801418439716,292.8571428571429,294.9640287769784,297.10144927536237,299.2700729927007,301.47058823529414,303.7037037037037,305.97014925373134,308.2706766917293,310.6060606060606,312.9770992366412,315.3846153846155,317.82945736434107,320.3125,322.8346456692913,325.39682539682536,328.,330.6451612903226,333.3333333333333,336.0655737704918,338.8429752066116,341.6666666666667,344.5378151260504,347.4576271186441,350.42735042735046,353.44827586206895,356.5217391304348,359.64912280701753,362.83185840707966,366.07142857142856,369.3693693693694,372.7272727272727,376.14678899082577,379.62962962962956,383.177570093458,386.79245283018867,390.47619047619054,394.23076923076917,398.05825242718447,401.96078431372547,405.94059405940595,410. +200.,201.00502512562812,202.02020202020202,203.0456852791878,204.08163265306123,205.12820512820517,206.1855670103093,207.25388601036272,208.33333333333334,209.4240837696335,210.52631578947367,211.64021164021167,212.7659574468085,213.903743315508,215.05376344086025,216.2162162162162,217.39130434782606,218.57923497267757,219.78021978021977,220.99447513812157,222.22222222222223,223.46368715083798,224.71910112359552,225.98870056497177,227.27272727272728,228.57142857142856,229.88505747126436,231.21387283236996,232.55813953488374,233.91812865497076,235.29411764705884,236.68639053254438,238.0952380952381,239.52095808383237,240.96385542168676,242.42424242424244,243.90243902439025,245.39877300613497,246.91358024691357,248.44720496894413,250.,251.5723270440252,253.1645569620253,254.77707006369425,256.4102564102564,258.06451612903226,259.7402597402597,261.437908496732,263.15789473684214,264.90066225165566,266.66666666666663,268.4563758389262,270.27027027027026,272.10884353741494,273.972602739726,275.86206896551727,277.77777777777777,279.72027972027973,281.69014084507046,283.6879432624113,285.7142857142857,287.76978417266184,289.8550724637682,291.97080291970804,294.11764705882354,296.2962962962963,298.5074626865672,300.7518796992481,303.03030303030306,305.34351145038164,307.69230769230774,310.07751937984494,312.5,314.9606299212598,317.46031746031747,320.,322.58064516129036,325.2032520325203,327.8688524590164,330.57851239669424,333.33333333333337,336.1344537815126,338.98305084745766,341.8803418803419,344.8275862068965,347.82608695652175,350.8771929824561,353.98230088495575,357.1428571428571,360.3603603603604,363.6363636363636,366.97247706422024,370.3703703703703,373.8317757009346,377.3584905660377,380.952380952381,384.6153846153846,388.3495145631068,392.156862745098,396.03960396039605,400. +195.,195.97989949748742,196.96969696969697,197.9695431472081,198.9795918367347,200.00000000000003,201.03092783505156,202.07253886010366,203.12500000000003,204.18848167539267,205.26315789473682,206.34920634920638,207.4468085106383,208.5561497326203,209.67741935483875,210.81081081081078,211.95652173913044,213.11475409836063,214.28571428571425,215.46961325966853,216.66666666666669,217.87709497206703,219.10112359550564,220.33898305084747,221.59090909090912,222.85714285714283,224.13793103448273,225.4335260115607,226.74418604651163,228.07017543859646,229.41176470588235,230.76923076923077,232.14285714285714,233.53293413173654,234.9397590361446,236.36363636363637,237.8048780487805,239.2638036809816,240.74074074074073,242.23602484472053,243.75,245.28301886792457,246.83544303797467,248.4076433121019,249.99999999999997,251.61290322580643,253.24675324675326,254.90196078431373,256.5789473684211,258.27814569536423,260.,261.74496644295306,263.5135135135135,265.3061224489796,267.12328767123284,268.9655172413793,270.8333333333333,272.72727272727275,274.6478873239437,276.59574468085106,278.57142857142856,280.57553956834533,282.60869565217394,284.6715328467153,286.764705882353,288.88888888888886,291.044776119403,293.2330827067669,295.4545454545455,297.70992366412213,300.00000000000006,302.3255813953488,304.6875,307.0866141732283,309.5238095238095,312.,314.5161290322581,317.0731707317073,319.672131147541,322.3140495867769,325.,327.7310924369748,330.5084745762712,333.33333333333337,336.2068965517241,339.13043478260875,342.10526315789474,345.1327433628319,348.21428571428567,351.3513513513514,354.54545454545456,357.7981651376147,361.1111111111111,364.4859813084113,367.92452830188677,371.4285714285715,374.99999999999994,378.6407766990291,382.35294117647055,386.13861386138615,390. +190.,190.95477386934672,191.91919191919195,192.89340101522842,193.87755102040816,194.8717948717949,195.87628865979383,196.89119170984458,197.91666666666669,198.95287958115182,200.,201.0582010582011,202.12765957446808,203.2085561497326,204.30107526881724,205.40540540540536,206.52173913043478,207.6502732240437,208.79120879120876,209.94475138121547,211.11111111111111,212.29050279329607,213.48314606741576,214.68926553672316,215.90909090909093,217.14285714285714,218.39080459770113,219.65317919075147,220.93023255813955,222.2222222222222,223.52941176470588,224.85207100591717,226.19047619047618,227.54491017964074,228.91566265060243,230.3030303030303,231.7073170731707,233.12883435582822,234.5679012345679,236.02484472049693,237.5,238.99371069182394,240.506329113924,242.03821656050954,243.58974358974356,245.16129032258064,246.75324675324674,248.36601307189542,250.00000000000003,251.65562913907286,253.33333333333331,255.03355704697987,256.7567567567568,258.5034013605442,260.2739726027397,262.0689655172414,263.88888888888886,265.73426573426576,267.6056338028169,269.50354609929076,271.42857142857144,273.38129496402877,275.36231884057975,277.37226277372264,279.4117647058824,281.48148148148147,283.5820895522388,285.7142857142857,287.87878787878793,290.07633587786256,292.3076923076924,294.5736434108527,296.875,299.2125984251968,301.58730158730157,304.,306.45161290322585,308.9430894308943,311.4754098360656,314.0495867768595,316.6666666666667,319.327731092437,322.0338983050848,324.7863247863248,327.5862068965517,330.4347826086957,333.3333333333333,336.283185840708,339.2857142857143,342.34234234234236,345.45454545454544,348.62385321100925,351.8518518518518,355.1401869158879,358.4905660377358,361.904761904762,365.38461538461536,368.93203883495147,372.54901960784315,376.23762376237624,380. +185.,185.92964824120602,186.8686868686869,187.8172588832487,188.77551020408163,189.74358974358978,190.7216494845361,191.70984455958552,192.70833333333334,193.717277486911,194.73684210526315,195.7671957671958,196.80851063829786,197.86096256684488,198.92473118279574,199.99999999999997,201.08695652173913,202.18579234972677,203.29670329670327,204.41988950276243,205.55555555555557,206.7039106145251,207.86516853932585,209.03954802259886,210.22727272727275,211.42857142857142,212.64367816091954,213.87283236994222,215.11627906976744,216.37426900584794,217.64705882352942,218.93491124260356,220.23809523809524,221.55688622754494,222.89156626506025,224.24242424242425,225.60975609756096,226.99386503067484,228.39506172839504,229.81366459627333,231.25,232.7044025157233,234.17721518987338,235.66878980891718,237.17948717948715,238.70967741935482,240.25974025974025,241.83006535947712,243.42105263157896,245.03311258278148,246.66666666666666,248.32214765100673,250.,251.70068027210883,253.42465753424656,255.17241379310346,256.94444444444446,258.7412587412588,260.5633802816902,262.41134751773046,264.2857142857143,266.1870503597122,268.11594202898556,270.0729927007299,272.05882352941177,274.0740740740741,276.11940298507466,278.1954887218045,280.30303030303037,282.44274809160305,284.6153846153847,286.8217054263566,289.0625,291.33858267716533,293.6507936507936,296.,298.3870967741936,300.8130081300813,303.27868852459017,305.78512396694214,308.33333333333337,310.92436974789916,313.55932203389835,316.2393162393163,318.9655172413793,321.7391304347826,324.5614035087719,327.4336283185841,330.35714285714283,333.33333333333337,336.3636363636364,339.4495412844037,342.59259259259255,345.79439252336454,349.05660377358487,352.38095238095246,355.7692307692307,359.22330097087377,362.7450980392157,366.33663366336634,370. +180.,180.9045226130653,181.81818181818184,182.74111675126903,183.67346938775512,184.61538461538464,185.56701030927837,186.52849740932646,187.5,188.48167539267016,189.4736842105263,190.4761904761905,191.48936170212767,192.51336898395718,193.54838709677423,194.59459459459455,195.65217391304347,196.7213114754098,197.80219780219778,198.8950276243094,200.,201.11731843575419,202.24719101123597,203.38983050847457,204.54545454545456,205.7142857142857,206.8965517241379,208.09248554913296,209.30232558139537,210.52631578947367,211.76470588235296,213.01775147928996,214.28571428571428,215.56886227544913,216.8674698795181,218.1818181818182,219.5121951219512,220.8588957055215,222.2222222222222,223.60248447204972,225.,226.41509433962267,227.84810126582275,229.29936305732483,230.76923076923075,232.25806451612902,233.76623376623377,235.29411764705884,236.84210526315792,238.41059602649008,240.,241.61073825503357,243.24324324324323,244.89795918367346,246.5753424657534,248.27586206896552,250.,251.7482517482518,253.5211267605634,255.3191489361702,257.14285714285717,258.9928057553957,260.86956521739137,262.77372262773724,264.7058823529412,266.66666666666663,268.65671641791045,270.6766917293233,272.72727272727275,274.8091603053435,276.92307692307696,279.06976744186045,281.25,283.4645669291338,285.7142857142857,288.,290.32258064516134,292.6829268292683,295.08196721311475,297.5206611570248,300.,302.52100840336135,305.0847457627119,307.69230769230774,310.34482758620686,313.0434782608696,315.7894736842105,318.5840707964602,321.4285714285714,324.3243243243244,327.27272727272725,330.2752293577982,333.3333333333333,336.44859813084116,339.62264150943395,342.8571428571429,346.15384615384613,349.5145631067961,352.94117647058823,356.43564356435644,360. +175.,175.8793969849246,176.7676767676768,177.66497461928932,178.57142857142858,179.4871794871795,180.41237113402065,181.34715025906738,182.29166666666669,183.2460732984293,184.21052631578945,185.18518518518522,186.17021276595744,187.16577540106948,188.1720430107527,189.18918918918916,190.2173913043478,191.25683060109287,192.3076923076923,193.37016574585635,194.44444444444446,195.53072625698323,196.6292134831461,197.74011299435028,198.86363636363637,200.,201.1494252873563,202.3121387283237,203.48837209302326,204.6783625730994,205.88235294117646,207.10059171597635,208.33333333333334,209.5808383233533,210.84337349397592,212.12121212121212,213.41463414634146,214.7239263803681,216.04938271604937,217.39130434782612,218.75,220.12578616352204,221.51898734177212,222.92993630573247,224.35897435897434,225.80645161290323,227.27272727272728,228.75816993464053,230.26315789473685,231.78807947019868,233.33333333333331,234.8993288590604,236.48648648648648,238.09523809523807,239.72602739726025,241.37931034482762,243.05555555555554,244.75524475524477,246.47887323943664,248.2269503546099,250.,251.79856115107913,253.62318840579715,255.47445255474452,257.3529411764706,259.25925925925924,261.1940298507463,263.1578947368421,265.1515151515152,267.17557251908397,269.2307692307693,271.3178294573643,273.4375,275.59055118110234,277.77777777777777,280.,282.2580645161291,284.5528455284553,286.88524590163934,289.25619834710744,291.6666666666667,294.11764705882354,296.6101694915254,299.1452991452992,301.7241379310345,304.34782608695656,307.0175438596491,309.7345132743363,312.5,315.31531531531533,318.1818181818182,321.1009174311927,324.074074074074,327.1028037383178,330.18867924528297,333.33333333333337,336.5384615384615,339.80582524271847,343.1372549019608,346.53465346534654,350. +170.,170.8542713567839,171.71717171717174,172.58883248730965,173.46938775510205,174.3589743589744,175.25773195876292,176.16580310880832,177.08333333333334,178.0104712041885,178.94736842105263,179.89417989417993,180.85106382978722,181.81818181818178,182.7956989247312,183.78378378378375,184.78260869565216,185.79234972677594,186.8131868131868,187.84530386740332,188.88888888888889,189.94413407821227,191.01123595505618,192.090395480226,193.1818181818182,194.28571428571428,195.4022988505747,196.53179190751447,197.67441860465118,198.83040935672514,200.,201.18343195266274,202.38095238095238,203.5928143712575,204.81927710843377,206.06060606060606,207.3170731707317,208.58895705521473,209.87654320987653,211.18012422360252,212.5,213.8364779874214,215.1898734177215,216.5605095541401,217.94871794871793,219.3548387096774,220.77922077922076,222.22222222222223,223.6842105263158,225.1655629139073,226.66666666666666,228.18791946308727,229.7297297297297,231.2925170068027,232.8767123287671,234.48275862068968,236.11111111111111,237.7622377622378,239.43661971830988,241.13475177304963,242.85714285714286,244.6043165467626,246.37681159420293,248.17518248175182,250.00000000000003,251.85185185185185,253.7313432835821,255.6390977443609,257.5757575757576,259.5419847328244,261.5384615384616,263.5658914728682,265.625,267.7165354330708,269.8412698412698,272.,274.19354838709677,276.4227642276423,278.6885245901639,280.9917355371901,283.33333333333337,285.7142857142857,288.135593220339,290.59829059829065,293.10344827586204,295.6521739130435,298.2456140350877,300.8849557522124,303.57142857142856,306.30630630630634,309.09090909090907,311.9266055045872,314.8148148148148,317.7570093457944,320.75471698113205,323.80952380952385,326.9230769230769,330.09708737864077,333.3333333333333,336.63366336633663,340. +165.,165.8291457286432,166.66666666666669,167.51269035532994,168.3673469387755,169.23076923076925,170.10309278350516,170.98445595854923,171.875,172.77486910994764,173.68421052631578,174.60317460317464,175.53191489361703,176.4705882352941,177.4193548387097,178.37837837837836,179.3478260869565,180.327868852459,181.31868131868129,182.32044198895028,183.33333333333334,184.35754189944132,185.3932584269663,186.4406779661017,187.50000000000003,188.57142857142856,189.65517241379308,190.75144508670522,191.86046511627907,192.98245614035088,194.11764705882354,195.2662721893491,196.42857142857142,197.6047904191617,198.7951807228916,200.,201.21951219512195,202.45398773006136,203.7037037037037,204.96894409937892,206.25,207.54716981132077,208.86075949367086,210.19108280254775,211.53846153846152,212.90322580645162,214.28571428571428,215.68627450980392,217.10526315789474,218.5430463576159,220.,221.4765100671141,222.97297297297297,224.48979591836735,226.02739726027397,227.58620689655174,229.16666666666666,230.7692307692308,232.39436619718313,234.04255319148933,235.71428571428572,237.41007194244602,239.13043478260875,240.87591240875912,242.64705882352942,244.44444444444443,246.26865671641792,248.12030075187968,250.00000000000003,251.90839694656486,253.8461538461539,255.81395348837208,257.8125,259.84251968503935,261.90476190476187,264.,266.1290322580645,268.2926829268293,270.4918032786885,272.72727272727275,275.,277.3109243697479,279.66101694915255,282.0512820512821,284.48275862068965,286.95652173913044,289.4736842105263,292.0353982300885,294.6428571428571,297.29729729729735,300.,302.75229357798173,305.55555555555554,308.41121495327104,311.3207547169811,314.28571428571433,317.30769230769226,320.3883495145631,323.52941176470586,326.73267326732673,330. +160.,160.8040201005025,161.61616161616163,162.43654822335026,163.26530612244898,164.10256410256412,164.94845360824743,165.80310880829018,166.66666666666669,167.5392670157068,168.42105263157893,169.31216931216935,170.2127659574468,171.1229946524064,172.0430107526882,172.97297297297294,173.91304347826087,174.86338797814204,175.8241758241758,176.79558011049724,177.77777777777777,178.7709497206704,179.77528089887642,180.7909604519774,181.81818181818184,182.85714285714283,183.90804597701148,184.97109826589596,186.046511627907,187.13450292397658,188.23529411764707,189.3491124260355,190.47619047619048,191.61676646706587,192.7710843373494,193.93939393939394,195.1219512195122,196.31901840490798,197.53086419753086,198.75776397515529,200.,201.25786163522014,202.53164556962022,203.8216560509554,205.1282051282051,206.4516129032258,207.7922077922078,209.15032679738562,210.5263157894737,211.9205298013245,213.33333333333331,214.76510067114094,216.2162162162162,217.68707482993196,219.17808219178082,220.6896551724138,222.22222222222223,223.7762237762238,225.35211267605635,226.95035460992904,228.57142857142858,230.2158273381295,231.88405797101453,233.57664233576642,235.29411764705884,237.037037037037,238.80597014925374,240.6015037593985,242.42424242424246,244.27480916030532,246.1538461538462,248.06201550387595,250.,251.96850393700785,253.96825396825395,256.,258.06451612903226,260.1626016260162,262.29508196721315,264.4628099173554,266.6666666666667,268.9075630252101,271.1864406779661,273.50427350427356,275.8620689655172,278.26086956521743,280.7017543859649,283.18584070796464,285.71428571428567,288.2882882882883,290.9090909090909,293.5779816513762,296.29629629629625,299.0654205607477,301.88679245283015,304.7619047619048,307.6923076923077,310.6796116504854,313.7254901960784,316.83168316831683,320. +155.,155.7788944723618,156.56565656565658,157.36040609137055,158.16326530612244,158.974358974359,159.7938144329897,160.62176165803112,161.45833333333334,162.30366492146598,163.1578947368421,164.02116402116405,164.89361702127658,165.7754010695187,166.66666666666669,167.56756756756755,168.47826086956522,169.3989071038251,170.3296703296703,171.2707182320442,172.22222222222223,173.18435754189943,174.15730337078654,175.14124293785312,176.13636363636365,177.14285714285714,178.16091954022988,179.19075144508673,180.2325581395349,181.28654970760232,182.3529411764706,183.4319526627219,184.52380952380952,185.62874251497007,186.74698795180726,187.87878787878788,189.02439024390245,190.1840490797546,191.35802469135803,192.54658385093168,193.75,194.96855345911953,196.2025316455696,197.45222929936304,198.7179487179487,200.,201.2987012987013,202.6143790849673,203.94736842105266,205.29801324503313,206.66666666666666,208.0536912751678,209.45945945945945,210.8843537414966,212.32876712328766,213.79310344827587,215.27777777777777,216.7832167832168,218.3098591549296,219.85815602836877,221.42857142857144,223.02158273381295,224.6376811594203,226.27737226277372,227.94117647058826,229.62962962962962,231.34328358208955,233.08270676691728,234.84848484848487,236.64122137404578,238.4615384615385,240.31007751937983,242.1875,244.09448818897636,246.03174603174602,248.,250.00000000000003,252.03252032520325,254.0983606557377,256.198347107438,258.33333333333337,260.5042016806723,262.7118644067797,264.95726495726495,267.2413793103448,269.5652173913044,271.9298245614035,274.3362831858407,276.7857142857143,279.2792792792793,281.8181818181818,284.4036697247707,287.037037037037,289.71962616822435,292.45283018867923,295.2380952380953,298.07692307692304,300.97087378640776,303.921568627451,306.9306930693069,310. +150.,150.7537688442211,151.51515151515153,152.28426395939084,153.06122448979593,153.84615384615387,154.63917525773198,155.44041450777203,156.25,157.06806282722513,157.89473684210526,158.73015873015876,159.5744680851064,160.427807486631,161.29032258064518,162.16216216216213,163.04347826086956,163.93442622950818,164.83516483516482,165.74585635359117,166.66666666666669,167.59776536312847,168.53932584269666,169.49152542372883,170.45454545454547,171.42857142857142,172.41379310344826,173.41040462427748,174.4186046511628,175.43859649122805,176.47058823529412,177.5147928994083,178.57142857142858,179.64071856287427,180.72289156626508,181.8181818181818,182.9268292682927,184.04907975460122,185.18518518518516,186.33540372670808,187.5,188.6792452830189,189.87341772151896,191.08280254777068,192.3076923076923,193.54838709677418,194.80519480519482,196.07843137254903,197.3684210526316,198.67549668874173,200.,201.34228187919464,202.7027027027027,204.0816326530612,205.4794520547945,206.89655172413794,208.33333333333331,209.79020979020981,211.26760563380284,212.76595744680847,214.28571428571428,215.82733812949638,217.39130434782612,218.97810218978103,220.58823529411765,222.2222222222222,223.8805970149254,225.56390977443607,227.2727272727273,229.00763358778624,230.7692307692308,232.5581395348837,234.375,236.22047244094486,238.09523809523807,240.,241.93548387096777,243.90243902439025,245.9016393442623,247.93388429752068,250.,252.10084033613447,254.23728813559325,256.4102564102564,258.6206896551724,260.8695652173913,263.1578947368421,265.4867256637168,267.85714285714283,270.2702702702703,272.7272727272727,275.2293577981652,277.77777777777777,280.373831775701,283.01886792452825,285.7142857142858,288.46153846153845,291.2621359223301,294.11764705882354,297.029702970297,300. +145.,145.7286432160804,146.46464646464648,147.20812182741116,147.9591836734694,148.71794871794873,149.48453608247425,150.25906735751298,151.04166666666669,151.8324607329843,152.6315789473684,153.43915343915347,154.25531914893617,155.0802139037433,155.91397849462368,156.75675675675674,157.6086956521739,158.46994535519124,159.34065934065933,160.22099447513813,161.11111111111111,162.01117318435755,162.92134831460675,163.8418079096045,164.77272727272728,165.7142857142857,166.66666666666666,167.63005780346822,168.6046511627907,169.5906432748538,170.58823529411765,171.5976331360947,172.61904761904762,173.65269461077847,174.6987951807229,175.75757575757575,176.8292682926829,177.91411042944785,179.01234567901233,180.12422360248448,181.25,182.38993710691827,183.54430379746833,184.71337579617833,185.89743589743588,187.09677419354838,188.3116883116883,189.54248366013073,190.78947368421055,192.05298013245036,193.33333333333331,194.63087248322148,195.94594594594594,197.27891156462584,198.63013698630135,200.,201.38888888888889,202.79720279720283,204.22535211267606,205.6737588652482,207.14285714285714,208.63309352517985,210.1449275362319,211.67883211678833,213.23529411764707,214.8148148148148,216.41791044776122,218.04511278195488,219.69696969696972,221.3740458015267,223.07692307692312,224.80620155038758,226.5625,228.34645669291336,230.15873015873015,232.,233.8709677419355,235.77235772357724,237.70491803278688,239.6694214876033,241.66666666666669,243.69747899159665,245.7627118644068,247.8632478632479,249.99999999999997,252.17391304347828,254.38596491228068,256.63716814159295,258.9285714285714,261.2612612612613,263.6363636363636,266.0550458715597,268.5185185185185,271.0280373831776,273.58490566037733,276.19047619047626,278.8461538461538,281.5533980582524,284.3137254901961,287.1287128712871,290. +140.,140.7035175879397,141.41414141414143,142.13197969543145,142.85714285714286,143.58974358974362,144.32989690721652,145.0777202072539,145.83333333333334,146.59685863874344,147.36842105263156,148.14814814814818,148.93617021276594,149.7326203208556,150.53763440860217,151.35135135135133,152.17391304347825,153.0054644808743,153.84615384615384,154.6961325966851,155.55555555555557,156.4245810055866,157.30337078651687,158.19209039548022,159.0909090909091,160.,160.91954022988506,161.84971098265896,162.79069767441862,163.74269005847952,164.7058823529412,165.68047337278108,166.66666666666666,167.66467065868264,168.67469879518075,169.6969696969697,170.73170731707316,171.77914110429447,172.8395061728395,173.91304347826087,175.,176.10062893081763,177.2151898734177,178.34394904458597,179.48717948717947,180.64516129032256,181.8181818181818,183.00653594771242,184.21052631578948,185.43046357615896,186.66666666666666,187.91946308724832,189.1891891891892,190.47619047619048,191.7808219178082,193.1034482758621,194.44444444444443,195.8041958041958,197.1830985915493,198.5815602836879,200.,201.4388489208633,202.89855072463772,204.37956204379563,205.8823529411765,207.4074074074074,208.95522388059703,210.52631578947367,212.12121212121215,213.74045801526717,215.38461538461542,217.05426356589146,218.75,220.47244094488187,222.2222222222222,224.,225.80645161290323,227.6422764227642,229.5081967213115,231.40495867768595,233.33333333333334,235.29411764705884,237.28813559322035,239.31623931623935,241.37931034482756,243.47826086956525,245.6140350877193,247.78761061946904,249.99999999999997,252.25225225225228,254.54545454545453,256.88073394495416,259.25925925925924,261.68224299065423,264.1509433962264,266.66666666666674,269.2307692307692,271.84466019417476,274.5098039215686,277.2277227722772,280. +135.,135.67839195979897,136.36363636363637,137.05583756345177,137.75510204081633,138.46153846153848,139.17525773195877,139.89637305699483,140.625,141.36125654450262,142.10526315789474,142.8571428571429,143.61702127659575,144.3850267379679,145.16129032258067,145.94594594594594,146.7391304347826,147.54098360655735,148.35164835164832,149.17127071823205,150.,150.83798882681563,151.68539325842698,152.54237288135593,153.40909090909093,154.28571428571428,155.17241379310343,156.06936416184973,156.97674418604652,157.89473684210526,158.82352941176472,159.76331360946747,160.71428571428572,161.67664670658684,162.65060240963857,163.63636363636365,164.6341463414634,165.64417177914112,166.66666666666666,167.70186335403727,168.75,169.811320754717,170.88607594936707,171.9745222929936,173.07692307692307,174.19354838709677,175.32467532467533,176.47058823529412,177.63157894736844,178.80794701986756,180.,181.20805369127518,182.43243243243242,183.6734693877551,184.93150684931507,186.20689655172416,187.5,188.81118881118883,190.14084507042256,191.48936170212764,192.85714285714286,194.24460431654674,195.6521739130435,197.08029197080293,198.52941176470588,200.,201.49253731343285,203.00751879699246,204.54545454545456,206.10687022900763,207.69230769230774,209.30232558139534,210.9375,212.59842519685037,214.28571428571428,216.,217.74193548387098,219.5121951219512,221.31147540983608,223.1404958677686,225.,226.890756302521,228.8135593220339,230.7692307692308,232.75862068965515,234.7826086956522,236.8421052631579,238.93805309734515,241.07142857142856,243.24324324324328,245.45454545454544,247.70642201834866,249.99999999999997,252.33644859813086,254.71698113207543,257.14285714285717,259.6153846153846,262.1359223300971,264.70588235294116,267.3267326732673,270. +130.,130.65326633165827,131.31313131313132,131.97969543147207,132.6530612244898,133.33333333333334,134.02061855670104,134.71502590673578,135.41666666666669,136.12565445026178,136.8421052631579,137.5661375661376,138.29787234042553,139.03743315508018,139.78494623655916,140.54054054054052,141.30434782608694,142.0765027322404,142.85714285714283,143.646408839779,144.44444444444446,145.25139664804468,146.06741573033707,146.89265536723164,147.72727272727275,148.57142857142856,149.42528735632183,150.28901734104048,151.16279069767444,152.046783625731,152.94117647058823,153.84615384615384,154.76190476190476,155.68862275449104,156.6265060240964,157.5757575757576,158.53658536585365,159.50920245398774,160.49382716049382,161.49068322981367,162.5,163.52201257861637,164.55696202531644,165.60509554140125,166.66666666666666,167.74193548387098,168.83116883116884,169.9346405228758,171.05263157894737,172.18543046357618,173.33333333333331,174.49664429530202,175.67567567567568,176.87074829931973,178.08219178082192,179.31034482758622,180.55555555555554,181.81818181818184,183.0985915492958,184.39716312056734,185.71428571428572,187.0503597122302,188.4057971014493,189.7810218978102,191.1764705882353,192.59259259259258,194.02985074626866,195.48872180451127,196.969696969697,198.4732824427481,200.00000000000003,201.5503875968992,203.125,204.72440944881888,206.34920634920633,208.,209.67741935483872,211.3821138211382,213.11475409836066,214.87603305785126,216.66666666666669,218.48739495798318,220.33898305084747,222.22222222222223,224.13793103448273,226.08695652173915,228.07017543859646,230.08849557522126,232.14285714285714,234.23423423423426,236.36363636363635,238.53211009174316,240.7407407407407,242.9906542056075,245.2830188679245,247.61904761904765,249.99999999999997,252.4271844660194,254.9019607843137,257.4257425742574,260. +125.,125.62814070351757,126.26262626262627,126.90355329949237,127.55102040816327,128.20512820512823,128.8659793814433,129.5336787564767,130.20833333333334,130.89005235602093,131.57894736842104,132.2751322751323,132.9787234042553,133.68983957219248,134.40860215053766,135.1351351351351,135.86956521739128,136.61202185792348,137.36263736263734,138.12154696132598,138.88888888888889,139.66480446927375,140.4494382022472,141.24293785310735,142.04545454545456,142.85714285714286,143.67816091954023,144.50867052023122,145.34883720930233,146.19883040935673,147.05882352941177,147.92899408284023,148.8095238095238,149.70059880239523,150.60240963855424,151.51515151515153,152.4390243902439,153.37423312883436,154.320987654321,155.27950310559007,156.25,157.23270440251574,158.2278481012658,159.2356687898089,160.25641025641025,161.29032258064515,162.33766233766232,163.39869281045753,164.47368421052633,165.56291390728478,166.66666666666666,167.78523489932886,168.9189189189189,170.06802721088434,171.23287671232876,172.41379310344828,173.61111111111111,174.82517482517486,176.05633802816902,177.30496453900707,178.57142857142858,179.85611510791367,181.1594202898551,182.4817518248175,183.82352941176472,185.18518518518516,186.56716417910448,187.96992481203006,189.39393939393943,190.83969465648855,192.30769230769235,193.7984496124031,195.3125,196.85039370078738,198.4126984126984,200.,201.61290322580646,203.2520325203252,204.91803278688525,206.61157024793388,208.33333333333334,210.08403361344537,211.86440677966104,213.67521367521368,215.51724137931032,217.39130434782612,219.29824561403507,221.23893805309734,223.2142857142857,225.22522522522524,227.27272727272725,229.35779816513767,231.48148148148147,233.64485981308414,235.84905660377356,238.09523809523813,240.38461538461536,242.71844660194176,245.09803921568627,247.5247524752475,250. +120.,120.60301507537687,121.21212121212122,121.82741116751268,122.44897959183673,123.0769230769231,123.71134020618558,124.35233160621763,125.00000000000001,125.6544502617801,126.3157894736842,126.984126984127,127.65957446808511,128.34224598930479,129.03225806451616,129.7297297297297,130.43478260869566,131.14754098360655,131.86813186813185,132.59668508287294,133.33333333333334,134.0782122905028,134.8314606741573,135.59322033898306,136.36363636363637,137.14285714285714,137.9310344827586,138.72832369942196,139.53488372093025,140.35087719298244,141.1764705882353,142.01183431952663,142.85714285714286,143.7125748502994,144.57831325301206,145.45454545454547,146.34146341463415,147.23926380368098,148.14814814814815,149.06832298136646,150.,150.9433962264151,151.89873417721518,152.86624203821654,153.84615384615384,154.83870967741936,155.84415584415584,156.86274509803923,157.89473684210526,158.94039735099338,160.,161.07382550335572,162.16216216216216,163.26530612244898,164.3835616438356,165.51724137931035,166.66666666666666,167.83216783216784,169.01408450704227,170.21276595744678,171.42857142857144,172.66187050359713,173.9130434782609,175.1824817518248,176.47058823529414,177.77777777777777,179.1044776119403,180.45112781954887,181.81818181818184,183.206106870229,184.61538461538464,186.04651162790697,187.5,188.97637795275588,190.47619047619045,192.,193.5483870967742,195.1219512195122,196.72131147540983,198.34710743801654,200.,201.68067226890756,203.3898305084746,205.12820512820514,206.8965517241379,208.69565217391306,210.52631578947367,212.38938053097345,214.28571428571428,216.21621621621625,218.1818181818182,220.18348623853214,222.2222222222222,224.29906542056077,226.4150943396226,228.5714285714286,230.76923076923075,233.0097087378641,235.2941176470588,237.6237623762376,240. +115.,115.57788944723617,116.16161616161617,116.75126903553299,117.34693877551021,117.94871794871797,118.55670103092785,119.17098445595856,119.79166666666667,120.41884816753927,121.05263157894736,121.69312169312171,122.34042553191489,122.99465240641709,123.65591397849464,124.32432432432431,125.,125.6830601092896,126.37362637362637,127.0718232044199,127.77777777777779,128.49162011173183,129.21348314606743,129.94350282485877,130.6818181818182,131.42857142857142,132.183908045977,132.94797687861274,133.72093023255815,134.50292397660817,135.29411764705884,136.09467455621302,136.9047619047619,137.7245508982036,138.5542168674699,139.3939393939394,140.2439024390244,141.1042944785276,141.9753086419753,142.85714285714286,143.75,144.65408805031447,145.56962025316454,146.49681528662418,147.43589743589743,148.38709677419354,149.35064935064935,150.32679738562092,151.31578947368422,152.317880794702,153.33333333333331,154.36241610738256,155.4054054054054,156.4625850340136,157.53424657534245,158.6206896551724,159.72222222222223,160.83916083916085,161.97183098591552,163.1205673758865,164.28571428571428,165.46762589928056,166.66666666666669,167.8832116788321,169.11764705882354,170.37037037037035,171.64179104477614,172.93233082706766,174.24242424242428,175.57251908396944,176.92307692307696,178.29457364341084,179.6875,181.1023622047244,182.53968253968253,184.,185.48387096774195,186.9918699186992,188.52459016393442,190.0826446280992,191.66666666666669,193.27731092436974,194.91525423728814,196.5811965811966,198.2758620689655,200.00000000000003,201.75438596491227,203.53982300884957,205.35714285714283,207.20720720720723,209.0909090909091,211.00917431192664,212.96296296296293,214.95327102803742,216.98113207547166,219.0476190476191,221.15384615384613,223.3009708737864,225.49019607843135,227.72277227722773,230. +110.,110.55276381909547,111.11111111111111,111.67512690355329,112.24489795918367,112.82051282051283,113.40206185567011,113.98963730569949,114.58333333333334,115.18324607329843,115.78947368421052,116.40211640211642,117.02127659574468,117.64705882352939,118.27956989247313,118.9189189189189,119.56521739130434,120.21857923497267,120.87912087912086,121.54696132596686,122.22222222222223,122.90502793296089,123.59550561797754,124.29378531073446,125.00000000000001,125.71428571428571,126.43678160919539,127.16763005780348,127.90697674418605,128.6549707602339,129.41176470588235,130.17751479289942,130.95238095238096,131.7365269461078,132.53012048192772,133.33333333333334,134.14634146341464,134.96932515337423,135.80246913580245,136.64596273291926,137.5,138.36477987421387,139.2405063291139,140.12738853503183,141.02564102564102,141.93548387096774,142.85714285714286,143.79084967320262,144.73684210526318,145.6953642384106,146.66666666666666,147.6510067114094,148.64864864864865,149.65986394557822,150.6849315068493,151.7241379310345,152.77777777777777,153.84615384615387,154.92957746478874,156.0283687943262,157.14285714285714,158.27338129496403,159.4202898550725,160.5839416058394,161.76470588235296,162.96296296296296,164.17910447761196,165.41353383458645,166.66666666666669,167.9389312977099,169.23076923076925,170.54263565891472,171.875,173.2283464566929,174.60317460317458,176.,177.4193548387097,178.86178861788616,180.32786885245903,181.8181818181818,183.33333333333334,184.87394957983193,186.4406779661017,188.03418803418805,189.65517241379308,191.30434782608697,192.98245614035088,194.69026548672568,196.42857142857142,198.1981981981982,200.,201.83486238532114,203.70370370370367,205.60747663551405,207.54716981132074,209.52380952380958,211.53846153846152,213.59223300970874,215.68627450980392,217.82178217821783,220. +105.,105.52763819095476,106.06060606060606,106.5989847715736,107.14285714285714,107.69230769230771,108.24742268041238,108.80829015544043,109.37500000000001,109.94764397905759,110.52631578947368,111.11111111111113,111.70212765957447,112.2994652406417,112.90322580645163,113.5135135135135,114.13043478260869,114.75409836065573,115.38461538461537,116.02209944751382,116.66666666666667,117.31843575418993,117.97752808988764,118.64406779661017,119.31818181818183,120.,120.68965517241378,121.38728323699424,122.09302325581396,122.80701754385964,123.52941176470588,124.26035502958581,125.,125.74850299401199,126.50602409638556,127.27272727272728,128.04878048780486,128.83435582822085,129.62962962962962,130.43478260869566,131.25,132.07547169811323,132.91139240506328,133.7579617834395,134.6153846153846,135.48387096774192,136.36363636363637,137.2549019607843,138.1578947368421,139.0728476821192,140.,140.93959731543626,141.8918918918919,142.85714285714286,143.83561643835617,144.82758620689657,145.83333333333331,146.85314685314688,147.88732394366198,148.93617021276594,150.,151.0791366906475,152.17391304347828,153.28467153284672,154.41176470588238,155.55555555555554,156.71641791044777,157.89473684210526,159.09090909090912,160.30534351145036,161.53846153846158,162.7906976744186,164.0625,165.3543307086614,166.66666666666666,168.,169.35483870967744,170.73170731707316,172.13114754098362,173.55371900826447,175.,176.47058823529412,177.96610169491527,179.4871794871795,181.03448275862067,182.60869565217394,184.21052631578945,185.8407079646018,187.5,189.18918918918922,190.9090909090909,192.66055045871562,194.44444444444443,196.26168224299067,198.1132075471698,200.00000000000003,201.9230769230769,203.88349514563106,205.88235294117646,207.92079207920793,210. +100.,100.50251256281406,101.01010101010101,101.5228426395939,102.04081632653062,102.56410256410258,103.09278350515466,103.62694300518136,104.16666666666667,104.71204188481676,105.26315789473684,105.82010582010584,106.38297872340425,106.951871657754,107.52688172043013,108.1081081081081,108.69565217391303,109.28961748633878,109.89010989010988,110.49723756906079,111.11111111111111,111.73184357541899,112.35955056179776,112.99435028248588,113.63636363636364,114.28571428571428,114.94252873563218,115.60693641618498,116.27906976744187,116.95906432748538,117.64705882352942,118.34319526627219,119.04761904761905,119.76047904191618,120.48192771084338,121.21212121212122,121.95121951219512,122.69938650306749,123.45679012345678,124.22360248447207,125.,125.7861635220126,126.58227848101265,127.38853503184713,128.2051282051282,129.03225806451613,129.87012987012986,130.718954248366,131.57894736842107,132.45033112582783,133.33333333333331,134.2281879194631,135.13513513513513,136.05442176870747,136.986301369863,137.93103448275863,138.88888888888889,139.86013986013987,140.84507042253523,141.84397163120565,142.85714285714286,143.88489208633092,144.9275362318841,145.98540145985402,147.05882352941177,148.14814814814815,149.2537313432836,150.37593984962405,151.51515151515153,152.67175572519082,153.84615384615387,155.03875968992247,156.25,157.4803149606299,158.73015873015873,160.,161.29032258064518,162.60162601626016,163.9344262295082,165.28925619834712,166.66666666666669,168.0672268907563,169.49152542372883,170.94017094017096,172.41379310344826,173.91304347826087,175.43859649122805,176.99115044247787,178.57142857142856,180.1801801801802,181.8181818181818,183.48623853211012,185.18518518518516,186.9158878504673,188.67924528301884,190.4761904761905,192.3076923076923,194.1747572815534,196.078431372549,198.01980198019803,200. +95.,95.47738693467336,95.95959595959597,96.44670050761421,96.93877551020408,97.43589743589745,97.93814432989691,98.44559585492229,98.95833333333334,99.47643979057591,100.,100.52910052910055,101.06382978723404,101.6042780748663,102.15053763440862,102.70270270270268,103.26086956521739,103.82513661202185,104.39560439560438,104.97237569060773,105.55555555555556,106.14525139664804,106.74157303370788,107.34463276836158,107.95454545454547,108.57142857142857,109.19540229885057,109.82658959537574,110.46511627906978,111.1111111111111,111.76470588235294,112.42603550295858,113.09523809523809,113.77245508982037,114.45783132530121,115.15151515151516,115.85365853658536,116.56441717791411,117.28395061728395,118.01242236024846,118.75,119.49685534591197,120.253164556962,121.01910828025477,121.79487179487178,122.58064516129032,123.37662337662337,124.18300653594771,125.00000000000001,125.82781456953643,126.66666666666666,127.51677852348993,128.3783783783784,129.2517006802721,130.13698630136986,131.0344827586207,131.94444444444443,132.86713286713288,133.80281690140845,134.75177304964538,135.71428571428572,136.69064748201438,137.68115942028987,138.68613138686132,139.7058823529412,140.74074074074073,141.7910447761194,142.85714285714286,143.93939393939397,145.03816793893128,146.1538461538462,147.28682170542635,148.4375,149.6062992125984,150.79365079365078,152.,153.22580645161293,154.47154471544715,155.7377049180328,157.02479338842974,158.33333333333334,159.6638655462185,161.0169491525424,162.3931623931624,163.79310344827584,165.21739130434784,166.66666666666666,168.141592920354,169.64285714285714,171.17117117117118,172.72727272727272,174.31192660550462,175.9259259259259,177.57009345794395,179.2452830188679,180.952380952381,182.69230769230768,184.46601941747574,186.27450980392157,188.11881188118812,190. +90.,90.45226130653265,90.90909090909092,91.37055837563452,91.83673469387756,92.30769230769232,92.78350515463919,93.26424870466323,93.75,94.24083769633508,94.73684210526315,95.23809523809526,95.74468085106383,96.25668449197859,96.77419354838712,97.29729729729728,97.82608695652173,98.3606557377049,98.90109890109889,99.4475138121547,100.,100.55865921787709,101.12359550561798,101.69491525423729,102.27272727272728,102.85714285714285,103.44827586206895,104.04624277456648,104.65116279069768,105.26315789473684,105.88235294117648,106.50887573964498,107.14285714285714,107.78443113772457,108.43373493975905,109.0909090909091,109.7560975609756,110.42944785276075,111.1111111111111,111.80124223602486,112.5,113.20754716981133,113.92405063291137,114.64968152866241,115.38461538461537,116.12903225806451,116.88311688311688,117.64705882352942,118.42105263157896,119.20529801324504,120.,120.80536912751678,121.62162162162161,122.44897959183673,123.2876712328767,124.13793103448276,125.,125.8741258741259,126.7605633802817,127.6595744680851,128.57142857142858,129.49640287769785,130.43478260869568,131.38686131386862,132.3529411764706,133.33333333333331,134.32835820895522,135.33834586466165,136.36363636363637,137.40458015267174,138.46153846153848,139.53488372093022,140.625,141.7322834645669,142.85714285714286,144.,145.16129032258067,146.34146341463415,147.54098360655738,148.7603305785124,150.,151.26050420168067,152.54237288135596,153.84615384615387,155.17241379310343,156.5217391304348,157.89473684210526,159.2920353982301,160.7142857142857,162.1621621621622,163.63636363636363,165.1376146788991,166.66666666666666,168.22429906542058,169.81132075471697,171.42857142857144,173.07692307692307,174.75728155339806,176.47058823529412,178.21782178217822,180. +85.,85.42713567839195,85.85858585858587,86.29441624365482,86.73469387755102,87.1794871794872,87.62886597938146,88.08290155440416,88.54166666666667,89.00523560209425,89.47368421052632,89.94708994708996,90.42553191489361,90.90909090909089,91.3978494623656,91.89189189189187,92.39130434782608,92.89617486338797,93.4065934065934,93.92265193370166,94.44444444444444,94.97206703910614,95.50561797752809,96.045197740113,96.5909090909091,97.14285714285714,97.70114942528735,98.26589595375724,98.83720930232559,99.41520467836257,100.,100.59171597633137,101.19047619047619,101.79640718562875,102.40963855421688,103.03030303030303,103.65853658536585,104.29447852760737,104.93827160493827,105.59006211180126,106.25,106.9182389937107,107.59493670886074,108.28025477707006,108.97435897435896,109.6774193548387,110.38961038961038,111.11111111111111,111.8421052631579,112.58278145695365,113.33333333333333,114.09395973154363,114.86486486486486,115.64625850340136,116.43835616438355,117.24137931034484,118.05555555555556,118.8811188811189,119.71830985915494,120.56737588652481,121.42857142857143,122.3021582733813,123.18840579710147,124.08759124087591,125.00000000000001,125.92592592592592,126.86567164179105,127.81954887218045,128.7878787878788,129.7709923664122,130.7692307692308,131.7829457364341,132.8125,133.8582677165354,134.9206349206349,136.,137.09677419354838,138.21138211382114,139.34426229508196,140.49586776859505,141.66666666666669,142.85714285714286,144.0677966101695,145.29914529914532,146.55172413793102,147.82608695652175,149.12280701754386,150.4424778761062,151.78571428571428,153.15315315315317,154.54545454545453,155.9633027522936,157.4074074074074,158.8785046728972,160.37735849056602,161.90476190476193,163.46153846153845,165.04854368932038,166.66666666666666,168.31683168316832,170. +80.,80.40201005025125,80.80808080808082,81.21827411167513,81.63265306122449,82.05128205128206,82.47422680412372,82.90155440414509,83.33333333333334,83.7696335078534,84.21052631578947,84.65608465608467,85.1063829787234,85.5614973262032,86.0215053763441,86.48648648648647,86.95652173913044,87.43169398907102,87.9120879120879,88.39779005524862,88.88888888888889,89.3854748603352,89.88764044943821,90.3954802259887,90.90909090909092,91.42857142857142,91.95402298850574,92.48554913294798,93.0232558139535,93.56725146198829,94.11764705882354,94.67455621301775,95.23809523809524,95.80838323353294,96.3855421686747,96.96969696969697,97.5609756097561,98.15950920245399,98.76543209876543,99.37888198757764,100.,100.62893081761007,101.26582278481011,101.9108280254777,102.56410256410255,103.2258064516129,103.8961038961039,104.57516339869281,105.26315789473685,105.96026490066225,106.66666666666666,107.38255033557047,108.1081081081081,108.84353741496598,109.58904109589041,110.3448275862069,111.11111111111111,111.8881118881119,112.67605633802818,113.47517730496452,114.28571428571429,115.10791366906474,115.94202898550726,116.78832116788321,117.64705882352942,118.5185185185185,119.40298507462687,120.30075187969925,121.21212121212123,122.13740458015266,123.0769230769231,124.03100775193798,125.,125.98425196850393,126.98412698412697,128.,129.03225806451613,130.0813008130081,131.14754098360658,132.2314049586777,133.33333333333334,134.45378151260505,135.59322033898306,136.75213675213678,137.9310344827586,139.13043478260872,140.35087719298244,141.59292035398232,142.85714285714283,144.14414414414415,145.45454545454544,146.7889908256881,148.14814814814812,149.53271028037386,150.94339622641508,152.3809523809524,153.84615384615384,155.3398058252427,156.8627450980392,158.41584158415841,160. +75.,75.37688442211055,75.75757575757576,76.14213197969542,76.53061224489797,76.92307692307693,77.31958762886599,77.72020725388602,78.125,78.53403141361257,78.94736842105263,79.36507936507938,79.7872340425532,80.2139037433155,80.64516129032259,81.08108108108107,81.52173913043478,81.96721311475409,82.41758241758241,82.87292817679558,83.33333333333334,83.79888268156424,84.26966292134833,84.74576271186442,85.22727272727273,85.71428571428571,86.20689655172413,86.70520231213874,87.2093023255814,87.71929824561403,88.23529411764706,88.75739644970415,89.28571428571429,89.82035928143713,90.36144578313254,90.9090909090909,91.46341463414635,92.02453987730061,92.59259259259258,93.16770186335404,93.75,94.33962264150945,94.93670886075948,95.54140127388534,96.15384615384615,96.77419354838709,97.40259740259741,98.03921568627452,98.6842105263158,99.33774834437087,100.,100.67114093959732,101.35135135135135,102.0408163265306,102.73972602739725,103.44827586206897,104.16666666666666,104.89510489510491,105.63380281690142,106.38297872340424,107.14285714285714,107.91366906474819,108.69565217391306,109.48905109489051,110.29411764705883,111.1111111111111,111.9402985074627,112.78195488721803,113.63636363636365,114.50381679389312,115.3846153846154,116.27906976744185,117.1875,118.11023622047243,119.04761904761904,120.,120.96774193548389,121.95121951219512,122.95081967213115,123.96694214876034,125.,126.05042016806723,127.11864406779662,128.2051282051282,129.3103448275862,130.43478260869566,131.57894736842104,132.7433628318584,133.92857142857142,135.13513513513516,136.36363636363635,137.6146788990826,138.88888888888889,140.1869158878505,141.50943396226413,142.8571428571429,144.23076923076923,145.63106796116506,147.05882352941177,148.5148514851485,150. +70.,70.35175879396985,70.70707070707071,71.06598984771573,71.42857142857143,71.79487179487181,72.16494845360826,72.53886010362694,72.91666666666667,73.29842931937172,73.68421052631578,74.07407407407409,74.46808510638297,74.8663101604278,75.26881720430109,75.67567567567566,76.08695652173913,76.50273224043715,76.92307692307692,77.34806629834254,77.77777777777779,78.2122905027933,78.65168539325843,79.09604519774011,79.54545454545455,80.,80.45977011494253,80.92485549132948,81.39534883720931,81.87134502923976,82.3529411764706,82.84023668639054,83.33333333333333,83.83233532934132,84.33734939759037,84.84848484848484,85.36585365853658,85.88957055214723,86.41975308641975,86.95652173913044,87.5,88.05031446540882,88.60759493670885,89.17197452229298,89.74358974358974,90.32258064516128,90.9090909090909,91.50326797385621,92.10526315789474,92.71523178807948,93.33333333333333,93.95973154362416,94.5945945945946,95.23809523809524,95.8904109589041,96.55172413793105,97.22222222222221,97.9020979020979,98.59154929577466,99.29078014184395,100.,100.71942446043165,101.44927536231886,102.18978102189782,102.94117647058825,103.7037037037037,104.47761194029852,105.26315789473684,106.06060606060608,106.87022900763358,107.69230769230771,108.52713178294573,109.375,110.23622047244093,111.1111111111111,112.,112.90322580645162,113.8211382113821,114.75409836065575,115.70247933884298,116.66666666666667,117.64705882352942,118.64406779661017,119.65811965811967,120.68965517241378,121.73913043478262,122.80701754385964,123.89380530973452,124.99999999999999,126.12612612612614,127.27272727272727,128.44036697247708,129.62962962962962,130.84112149532712,132.0754716981132,133.33333333333337,134.6153846153846,135.92233009708738,137.2549019607843,138.6138613861386,140. +65.,65.32663316582914,65.65656565656566,65.98984771573603,66.3265306122449,66.66666666666667,67.01030927835052,67.35751295336789,67.70833333333334,68.06282722513089,68.42105263157895,68.7830687830688,69.14893617021276,69.51871657754009,69.89247311827958,70.27027027027026,70.65217391304347,71.0382513661202,71.42857142857142,71.8232044198895,72.22222222222223,72.62569832402234,73.03370786516854,73.44632768361582,73.86363636363637,74.28571428571428,74.71264367816092,75.14450867052024,75.58139534883722,76.0233918128655,76.47058823529412,76.92307692307692,77.38095238095238,77.84431137724552,78.3132530120482,78.7878787878788,79.26829268292683,79.75460122699387,80.24691358024691,80.74534161490683,81.25,81.76100628930818,82.27848101265822,82.80254777070063,83.33333333333333,83.87096774193549,84.41558441558442,84.9673202614379,85.52631578947368,86.09271523178809,86.66666666666666,87.24832214765101,87.83783783783784,88.43537414965986,89.04109589041096,89.65517241379311,90.27777777777777,90.90909090909092,91.5492957746479,92.19858156028367,92.85714285714286,93.5251798561151,94.20289855072465,94.8905109489051,95.58823529411765,96.29629629629629,97.01492537313433,97.74436090225564,98.4848484848485,99.23664122137404,100.00000000000001,100.7751937984496,101.5625,102.36220472440944,103.17460317460316,104.,104.83870967741936,105.6910569105691,106.55737704918033,107.43801652892563,108.33333333333334,109.24369747899159,110.16949152542374,111.11111111111111,112.06896551724137,113.04347826086958,114.03508771929823,115.04424778761063,116.07142857142857,117.11711711711713,118.18181818181817,119.26605504587158,120.37037037037035,121.49532710280376,122.64150943396226,123.80952380952382,124.99999999999999,126.2135922330097,127.45098039215685,128.7128712871287,130. +60.,60.30150753768844,60.60606060606061,60.91370558375634,61.224489795918366,61.53846153846155,61.85567010309279,62.176165803108816,62.50000000000001,62.82722513089005,63.1578947368421,63.4920634920635,63.829787234042556,64.17112299465239,64.51612903225808,64.86486486486486,65.21739130434783,65.57377049180327,65.93406593406593,66.29834254143647,66.66666666666667,67.0391061452514,67.41573033707866,67.79661016949153,68.18181818181819,68.57142857142857,68.9655172413793,69.36416184971098,69.76744186046513,70.17543859649122,70.58823529411765,71.00591715976331,71.42857142857143,71.8562874251497,72.28915662650603,72.72727272727273,73.17073170731707,73.61963190184049,74.07407407407408,74.53416149068323,75.,75.47169811320755,75.94936708860759,76.43312101910827,76.92307692307692,77.41935483870968,77.92207792207792,78.43137254901961,78.94736842105263,79.47019867549669,80.,80.53691275167786,81.08108108108108,81.63265306122449,82.1917808219178,82.75862068965517,83.33333333333333,83.91608391608392,84.50704225352113,85.10638297872339,85.71428571428572,86.33093525179856,86.95652173913045,87.5912408759124,88.23529411764707,88.88888888888889,89.55223880597015,90.22556390977444,90.90909090909092,91.6030534351145,92.30769230769232,93.02325581395348,93.75,94.48818897637794,95.23809523809523,96.,96.7741935483871,97.5609756097561,98.36065573770492,99.17355371900827,100.,100.84033613445378,101.6949152542373,102.56410256410257,103.44827586206895,104.34782608695653,105.26315789473684,106.19469026548673,107.14285714285714,108.10810810810813,109.0909090909091,110.09174311926607,111.1111111111111,112.14953271028038,113.2075471698113,114.2857142857143,115.38461538461537,116.50485436893204,117.6470588235294,118.8118811881188,120. +55.,55.27638190954774,55.55555555555556,55.837563451776646,56.12244897959184,56.410256410256416,56.701030927835056,56.994818652849744,57.29166666666667,57.59162303664922,57.89473684210526,58.20105820105821,58.51063829787234,58.823529411764696,59.13978494623657,59.45945945945945,59.78260869565217,60.10928961748633,60.43956043956043,60.77348066298343,61.111111111111114,61.452513966480446,61.79775280898877,62.14689265536723,62.50000000000001,62.857142857142854,63.218390804597696,63.58381502890174,63.95348837209303,64.32748538011695,64.70588235294117,65.08875739644971,65.47619047619048,65.8682634730539,66.26506024096386,66.66666666666667,67.07317073170732,67.48466257668711,67.90123456790123,68.32298136645963,68.75,69.18238993710693,69.62025316455696,70.06369426751591,70.51282051282051,70.96774193548387,71.42857142857143,71.89542483660131,72.36842105263159,72.8476821192053,73.33333333333333,73.8255033557047,74.32432432432432,74.82993197278911,75.34246575342465,75.86206896551725,76.38888888888889,76.92307692307693,77.46478873239437,78.0141843971631,78.57142857142857,79.13669064748201,79.71014492753625,80.2919708029197,80.88235294117648,81.48148148148148,82.08955223880598,82.70676691729322,83.33333333333334,83.96946564885495,84.61538461538463,85.27131782945736,85.9375,86.61417322834644,87.30158730158729,88.,88.70967741935485,89.43089430894308,90.16393442622952,90.9090909090909,91.66666666666667,92.43697478991596,93.22033898305085,94.01709401709402,94.82758620689654,95.65217391304348,96.49122807017544,97.34513274336284,98.21428571428571,99.0990990990991,100.,100.91743119266057,101.85185185185183,102.80373831775702,103.77358490566037,104.76190476190479,105.76923076923076,106.79611650485437,107.84313725490196,108.91089108910892,110. +50.,50.25125628140703,50.505050505050505,50.76142131979695,51.02040816326531,51.28205128205129,51.54639175257733,51.81347150259068,52.083333333333336,52.35602094240838,52.63157894736842,52.91005291005292,53.191489361702125,53.475935828877,53.76344086021506,54.05405405405405,54.347826086956516,54.64480874316939,54.94505494505494,55.24861878453039,55.55555555555556,55.865921787709496,56.17977528089888,56.49717514124294,56.81818181818182,57.14285714285714,57.47126436781609,57.80346820809249,58.139534883720934,58.47953216374269,58.82352941176471,59.171597633136095,59.523809523809526,59.88023952095809,60.24096385542169,60.60606060606061,60.97560975609756,61.34969325153374,61.72839506172839,62.111801242236034,62.5,62.8930817610063,63.291139240506325,63.69426751592356,64.1025641025641,64.51612903225806,64.93506493506493,65.359477124183,65.78947368421053,66.22516556291392,66.66666666666666,67.11409395973155,67.56756756756756,68.02721088435374,68.4931506849315,68.96551724137932,69.44444444444444,69.93006993006993,70.42253521126761,70.92198581560282,71.42857142857143,71.94244604316546,72.46376811594205,72.99270072992701,73.52941176470588,74.07407407407408,74.6268656716418,75.18796992481202,75.75757575757576,76.33587786259541,76.92307692307693,77.51937984496124,78.125,78.74015748031495,79.36507936507937,80.,80.64516129032259,81.30081300813008,81.9672131147541,82.64462809917356,83.33333333333334,84.03361344537815,84.74576271186442,85.47008547008548,86.20689655172413,86.95652173913044,87.71929824561403,88.49557522123894,89.28571428571428,90.0900900900901,90.9090909090909,91.74311926605506,92.59259259259258,93.45794392523365,94.33962264150942,95.23809523809526,96.15384615384615,97.0873786407767,98.0392156862745,99.00990099009901,100. +45.,45.226130653266324,45.45454545454546,45.68527918781726,45.91836734693878,46.15384615384616,46.39175257731959,46.632124352331616,46.875,47.12041884816754,47.368421052631575,47.61904761904763,47.87234042553192,48.128342245989295,48.38709677419356,48.64864864864864,48.91304347826087,49.18032786885245,49.450549450549445,49.72375690607735,50.,50.279329608938546,50.56179775280899,50.847457627118644,51.13636363636364,51.42857142857142,51.72413793103448,52.02312138728324,52.32558139534884,52.63157894736842,52.94117647058824,53.25443786982249,53.57142857142857,53.892215568862284,54.216867469879524,54.54545454545455,54.8780487804878,55.21472392638037,55.55555555555555,55.90062111801243,56.25,56.60377358490567,56.96202531645569,57.324840764331206,57.692307692307686,58.064516129032256,58.44155844155844,58.82352941176471,59.21052631578948,59.60264900662252,60.,60.40268456375839,60.81081081081081,61.224489795918366,61.64383561643835,62.06896551724138,62.5,62.93706293706295,63.38028169014085,63.82978723404255,64.28571428571429,64.74820143884892,65.21739130434784,65.69343065693431,66.1764705882353,66.66666666666666,67.16417910447761,67.66917293233082,68.18181818181819,68.70229007633587,69.23076923076924,69.76744186046511,70.3125,70.86614173228345,71.42857142857143,72.,72.58064516129033,73.17073170731707,73.77049180327869,74.3801652892562,75.,75.63025210084034,76.27118644067798,76.92307692307693,77.58620689655172,78.2608695652174,78.94736842105263,79.64601769911505,80.35714285714285,81.0810810810811,81.81818181818181,82.56880733944955,83.33333333333333,84.11214953271029,84.90566037735849,85.71428571428572,86.53846153846153,87.37864077669903,88.23529411764706,89.10891089108911,90. +40.,40.201005025125625,40.40404040404041,40.609137055837564,40.816326530612244,41.02564102564103,41.23711340206186,41.450777202072544,41.66666666666667,41.8848167539267,42.10526315789473,42.328042328042336,42.5531914893617,42.7807486631016,43.01075268817205,43.243243243243235,43.47826086956522,43.71584699453551,43.95604395604395,44.19889502762431,44.44444444444444,44.6927374301676,44.943820224719104,45.19774011299435,45.45454545454546,45.71428571428571,45.97701149425287,46.24277456647399,46.51162790697675,46.783625730994146,47.05882352941177,47.337278106508876,47.61904761904762,47.90419161676647,48.19277108433735,48.484848484848484,48.78048780487805,49.079754601226995,49.382716049382715,49.68944099378882,50.,50.314465408805034,50.632911392405056,50.95541401273885,51.28205128205128,51.61290322580645,51.94805194805195,52.287581699346404,52.631578947368425,52.980132450331126,53.33333333333333,53.691275167785236,54.05405405405405,54.42176870748299,54.794520547945204,55.17241379310345,55.55555555555556,55.94405594405595,56.33802816901409,56.73758865248226,57.142857142857146,57.55395683453237,57.97101449275363,58.394160583941606,58.82352941176471,59.25925925925925,59.701492537313435,60.150375939849624,60.606060606060616,61.06870229007633,61.53846153846155,62.01550387596899,62.5,62.99212598425196,63.49206349206349,64.,64.51612903225806,65.04065040650406,65.57377049180329,66.11570247933885,66.66666666666667,67.22689075630252,67.79661016949153,68.37606837606839,68.9655172413793,69.56521739130436,70.17543859649122,70.79646017699116,71.42857142857142,72.07207207207207,72.72727272727272,73.39449541284405,74.07407407407406,74.76635514018693,75.47169811320754,76.1904761904762,76.92307692307692,77.66990291262135,78.4313725490196,79.20792079207921,80. +35.,35.175879396984925,35.35353535353536,35.53299492385786,35.714285714285715,35.897435897435905,36.08247422680413,36.26943005181347,36.458333333333336,36.64921465968586,36.84210526315789,37.037037037037045,37.234042553191486,37.4331550802139,37.63440860215054,37.83783783783783,38.04347826086956,38.25136612021858,38.46153846153846,38.67403314917127,38.88888888888889,39.10614525139665,39.325842696629216,39.548022598870055,39.77272727272727,40.,40.229885057471265,40.46242774566474,40.697674418604656,40.93567251461988,41.1764705882353,41.42011834319527,41.666666666666664,41.91616766467066,42.168674698795186,42.42424242424242,42.68292682926829,42.94478527607362,43.20987654320987,43.47826086956522,43.75,44.02515723270441,44.303797468354425,44.58598726114649,44.87179487179487,45.16129032258064,45.45454545454545,45.751633986928105,46.05263157894737,46.35761589403974,46.666666666666664,46.97986577181208,47.2972972972973,47.61904761904762,47.94520547945205,48.27586206896552,48.61111111111111,48.95104895104895,49.29577464788733,49.64539007092198,50.,50.35971223021583,50.72463768115943,51.09489051094891,51.47058823529412,51.85185185185185,52.23880597014926,52.63157894736842,53.03030303030304,53.43511450381679,53.846153846153854,54.263565891472865,54.6875,55.11811023622047,55.55555555555555,56.,56.45161290322581,56.91056910569105,57.37704918032787,57.85123966942149,58.333333333333336,58.82352941176471,59.32203389830509,59.82905982905984,60.34482758620689,60.86956521739131,61.40350877192982,61.94690265486726,62.49999999999999,63.06306306306307,63.63636363636363,64.22018348623854,64.81481481481481,65.42056074766356,66.0377358490566,66.66666666666669,67.3076923076923,67.96116504854369,68.62745098039215,69.3069306930693,70. +30.,30.15075376884422,30.303030303030305,30.45685279187817,30.612244897959183,30.769230769230774,30.927835051546396,31.088082901554408,31.250000000000004,31.413612565445025,31.57894736842105,31.74603174603175,31.914893617021278,32.085561497326196,32.25806451612904,32.43243243243243,32.608695652173914,32.78688524590164,32.967032967032964,33.149171270718234,33.333333333333336,33.5195530726257,33.70786516853933,33.898305084745765,34.09090909090909,34.285714285714285,34.48275862068965,34.68208092485549,34.88372093023256,35.08771929824561,35.294117647058826,35.50295857988166,35.714285714285715,35.92814371257485,36.144578313253014,36.36363636363637,36.58536585365854,36.809815950920246,37.03703703703704,37.267080745341616,37.5,37.735849056603776,37.974683544303794,38.216560509554135,38.46153846153846,38.70967741935484,38.96103896103896,39.21568627450981,39.473684210526315,39.735099337748345,40.,40.26845637583893,40.54054054054054,40.816326530612244,41.0958904109589,41.37931034482759,41.666666666666664,41.95804195804196,42.25352112676057,42.553191489361694,42.85714285714286,43.16546762589928,43.478260869565226,43.7956204379562,44.117647058823536,44.44444444444444,44.776119402985074,45.11278195488722,45.45454545454546,45.80152671755725,46.15384615384616,46.51162790697674,46.875,47.24409448818897,47.61904761904761,48.,48.38709677419355,48.78048780487805,49.18032786885246,49.586776859504134,50.,50.42016806722689,50.84745762711865,51.282051282051285,51.72413793103448,52.173913043478265,52.63157894736842,53.097345132743364,53.57142857142857,54.05405405405406,54.54545454545455,55.045871559633035,55.55555555555555,56.07476635514019,56.60377358490565,57.14285714285715,57.692307692307686,58.25242718446602,58.8235294117647,59.4059405940594,60. +25.,25.125628140703515,25.252525252525253,25.380710659898476,25.510204081632654,25.641025641025646,25.773195876288664,25.90673575129534,26.041666666666668,26.17801047120419,26.31578947368421,26.45502645502646,26.595744680851062,26.7379679144385,26.88172043010753,27.027027027027025,27.173913043478258,27.322404371584696,27.47252747252747,27.624309392265197,27.77777777777778,27.932960893854748,28.08988764044944,28.24858757062147,28.40909090909091,28.57142857142857,28.735632183908045,28.901734104046245,29.069767441860467,29.239766081871345,29.411764705882355,29.585798816568047,29.761904761904763,29.940119760479046,30.120481927710845,30.303030303030305,30.48780487804878,30.67484662576687,30.864197530864196,31.055900621118017,31.25,31.44654088050315,31.645569620253163,31.84713375796178,32.05128205128205,32.25806451612903,32.467532467532465,32.6797385620915,32.89473684210527,33.11258278145696,33.33333333333333,33.557046979865774,33.78378378378378,34.01360544217687,34.24657534246575,34.48275862068966,34.72222222222222,34.96503496503497,35.21126760563381,35.46099290780141,35.714285714285715,35.97122302158273,36.23188405797102,36.496350364963504,36.76470588235294,37.03703703703704,37.3134328358209,37.59398496240601,37.87878787878788,38.167938931297705,38.46153846153847,38.75968992248062,39.0625,39.370078740157474,39.682539682539684,40.,40.322580645161295,40.65040650406504,40.98360655737705,41.32231404958678,41.66666666666667,42.016806722689076,42.37288135593221,42.73504273504274,43.103448275862064,43.47826086956522,43.859649122807014,44.24778761061947,44.64285714285714,45.04504504504505,45.45454545454545,45.87155963302753,46.29629629629629,46.728971962616825,47.16981132075471,47.61904761904763,48.07692307692307,48.54368932038835,49.01960784313725,49.504950495049506,50. +20.,20.100502512562812,20.202020202020204,20.304568527918782,20.408163265306122,20.512820512820515,20.61855670103093,20.725388601036272,20.833333333333336,20.94240837696335,21.052631578947366,21.164021164021168,21.27659574468085,21.3903743315508,21.505376344086024,21.621621621621617,21.73913043478261,21.857923497267755,21.978021978021975,22.099447513812155,22.22222222222222,22.3463687150838,22.471910112359552,22.598870056497177,22.72727272727273,22.857142857142854,22.988505747126435,23.121387283236995,23.255813953488374,23.391812865497073,23.529411764705884,23.668639053254438,23.80952380952381,23.952095808383234,24.096385542168676,24.242424242424242,24.390243902439025,24.539877300613497,24.691358024691358,24.84472049689441,25.,25.157232704402517,25.316455696202528,25.477707006369425,25.64102564102564,25.806451612903224,25.974025974025974,26.143790849673202,26.315789473684212,26.490066225165563,26.666666666666664,26.845637583892618,27.027027027027025,27.210884353741495,27.397260273972602,27.586206896551726,27.77777777777778,27.972027972027973,28.169014084507044,28.36879432624113,28.571428571428573,28.776978417266186,28.985507246376816,29.197080291970803,29.411764705882355,29.629629629629626,29.850746268656717,30.075187969924812,30.303030303030308,30.534351145038165,30.769230769230774,31.007751937984494,31.25,31.49606299212598,31.746031746031743,32.,32.25806451612903,32.52032520325203,32.786885245901644,33.057851239669425,33.333333333333336,33.61344537815126,33.898305084745765,34.188034188034194,34.48275862068965,34.78260869565218,35.08771929824561,35.39823008849558,35.71428571428571,36.03603603603604,36.36363636363636,36.697247706422026,37.03703703703703,37.383177570093466,37.73584905660377,38.0952380952381,38.46153846153846,38.83495145631068,39.2156862745098,39.603960396039604,40. +15.,15.07537688442211,15.151515151515152,15.228426395939085,15.306122448979592,15.384615384615387,15.463917525773198,15.544041450777204,15.625000000000002,15.706806282722512,15.789473684210526,15.873015873015875,15.957446808510639,16.042780748663098,16.12903225806452,16.216216216216214,16.304347826086957,16.39344262295082,16.483516483516482,16.574585635359117,16.666666666666668,16.75977653631285,16.853932584269664,16.949152542372882,17.045454545454547,17.142857142857142,17.241379310344826,17.341040462427745,17.44186046511628,17.543859649122805,17.647058823529413,17.75147928994083,17.857142857142858,17.964071856287426,18.072289156626507,18.181818181818183,18.29268292682927,18.404907975460123,18.51851851851852,18.633540372670808,18.75,18.867924528301888,18.987341772151897,19.108280254777068,19.23076923076923,19.35483870967742,19.48051948051948,19.607843137254903,19.736842105263158,19.867549668874172,20.,20.134228187919465,20.27027027027027,20.408163265306122,20.54794520547945,20.689655172413794,20.833333333333332,20.97902097902098,21.126760563380284,21.276595744680847,21.42857142857143,21.58273381294964,21.739130434782613,21.8978102189781,22.058823529411768,22.22222222222222,22.388059701492537,22.55639097744361,22.72727272727273,22.900763358778626,23.07692307692308,23.25581395348837,23.4375,23.622047244094485,23.809523809523807,24.,24.193548387096776,24.390243902439025,24.59016393442623,24.793388429752067,25.,25.210084033613445,25.423728813559325,25.641025641025642,25.86206896551724,26.086956521739133,26.31578947368421,26.548672566371682,26.785714285714285,27.02702702702703,27.272727272727273,27.522935779816518,27.777777777777775,28.037383177570096,28.301886792452827,28.571428571428577,28.846153846153843,29.12621359223301,29.41176470588235,29.7029702970297,30. +10.,10.050251256281406,10.101010101010102,10.152284263959391,10.204081632653061,10.256410256410257,10.309278350515465,10.362694300518136,10.416666666666668,10.471204188481675,10.526315789473683,10.582010582010584,10.638297872340425,10.6951871657754,10.752688172043012,10.810810810810809,10.869565217391305,10.928961748633878,10.989010989010987,11.049723756906078,11.11111111111111,11.1731843575419,11.235955056179776,11.299435028248588,11.363636363636365,11.428571428571427,11.494252873563218,11.560693641618498,11.627906976744187,11.695906432748536,11.764705882352942,11.834319526627219,11.904761904761905,11.976047904191617,12.048192771084338,12.121212121212121,12.195121951219512,12.269938650306749,12.345679012345679,12.422360248447205,12.5,12.578616352201259,12.658227848101264,12.738853503184712,12.82051282051282,12.903225806451612,12.987012987012987,13.071895424836601,13.157894736842106,13.245033112582782,13.333333333333332,13.422818791946309,13.513513513513512,13.605442176870747,13.698630136986301,13.793103448275863,13.88888888888889,13.986013986013987,14.084507042253522,14.184397163120565,14.285714285714286,14.388489208633093,14.492753623188408,14.598540145985401,14.705882352941178,14.814814814814813,14.925373134328359,15.037593984962406,15.151515151515154,15.267175572519083,15.384615384615387,15.503875968992247,15.625,15.74803149606299,15.873015873015872,16.,16.129032258064516,16.260162601626014,16.393442622950822,16.528925619834713,16.666666666666668,16.80672268907563,16.949152542372882,17.094017094017097,17.241379310344826,17.39130434782609,17.543859649122805,17.69911504424779,17.857142857142854,18.01801801801802,18.18181818181818,18.348623853211013,18.518518518518515,18.691588785046733,18.867924528301884,19.04761904761905,19.23076923076923,19.41747572815534,19.6078431372549,19.801980198019802,20. +5.,5.025125628140703,5.050505050505051,5.0761421319796955,5.1020408163265305,5.128205128205129,5.154639175257732,5.181347150259068,5.208333333333334,5.2356020942408374,5.263157894736842,5.291005291005292,5.319148936170213,5.3475935828877,5.376344086021506,5.405405405405404,5.434782608695652,5.464480874316939,5.494505494505494,5.524861878453039,5.555555555555555,5.58659217877095,5.617977528089888,5.649717514124294,5.6818181818181825,5.7142857142857135,5.747126436781609,5.780346820809249,5.813953488372094,5.847953216374268,5.882352941176471,5.9171597633136095,5.9523809523809526,5.9880239520958085,6.024096385542169,6.0606060606060606,6.097560975609756,6.134969325153374,6.172839506172839,6.211180124223603,6.25,6.289308176100629,6.329113924050632,6.369426751592356,6.41025641025641,6.451612903225806,6.4935064935064934,6.5359477124183005,6.578947368421053,6.622516556291391,6.666666666666666,6.7114093959731544,6.756756756756756,6.802721088435374,6.8493150684931505,6.8965517241379315,6.944444444444445,6.993006993006993,7.042253521126761,7.092198581560282,7.142857142857143,7.194244604316546,7.246376811594204,7.299270072992701,7.352941176470589,7.4074074074074066,7.462686567164179,7.518796992481203,7.575757575757577,7.633587786259541,7.692307692307693,7.751937984496124,7.8125,7.874015748031495,7.936507936507936,8.,8.064516129032258,8.130081300813007,8.196721311475411,8.264462809917356,8.333333333333334,8.403361344537815,8.474576271186441,8.547008547008549,8.620689655172413,8.695652173913045,8.771929824561402,8.849557522123895,8.928571428571427,9.00900900900901,9.09090909090909,9.174311926605506,9.259259259259258,9.345794392523366,9.433962264150942,9.523809523809526,9.615384615384615,9.70873786407767,9.80392156862745,9.900990099009901,10. +0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0. diff --git a/projects/thesis/__init__.py b/projects/thesis/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/projects/thesis/adapters/__init__.py b/projects/thesis/adapters/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/projects/thesis/adapters/ac.py b/projects/thesis/adapters/ac.py new file mode 100644 index 00000000..8e7be980 --- /dev/null +++ b/projects/thesis/adapters/ac.py @@ -0,0 +1,16 @@ +"""Data adapter for Allen-Cahn. See configs/ac.yaml.""" + +import os +import numpy as np + +_DATA_DIR = os.path.abspath(os.path.join( + os.path.dirname(__file__), '..', '..', 'pic', 'data', 'ac' +)) + + +def load_data(): + t = np.linspace(0., 1., 51) + x = np.linspace(-1., 0.984375, 128) + data = np.load(os.path.join(_DATA_DIR, 'ac_data.npy')) + grids = np.meshgrid(t, x, indexing='ij') + return tuple(grids), data, ['u'], 1 diff --git a/projects/thesis/adapters/burgers_inviscid.py b/projects/thesis/adapters/burgers_inviscid.py new file mode 100644 index 00000000..fb01e444 --- /dev/null +++ b/projects/thesis/adapters/burgers_inviscid.py @@ -0,0 +1,18 @@ +"""Data adapter for Burgers inviscid. See configs/burgers_inviscid.yaml.""" + +import os +import numpy as np +import pandas as pd + +_DATA_DIR = os.path.abspath(os.path.join( + os.path.dirname(__file__), '..', '..', 'pic', 'data', 'burgers' +)) + + +def load_data(): + df = pd.read_csv(os.path.join(_DATA_DIR, 'burgers_sln_100.csv'), header=None) + data = np.transpose(df.values) + t = np.linspace(0, 1, 101) + x = np.linspace(-1000, 0, 101) + grids = np.meshgrid(t, x, indexing='ij') + return tuple(grids), data, ['u'], 1 diff --git a/projects/thesis/adapters/burgers_viscous.py b/projects/thesis/adapters/burgers_viscous.py new file mode 100644 index 00000000..32602cf9 --- /dev/null +++ b/projects/thesis/adapters/burgers_viscous.py @@ -0,0 +1,18 @@ +"""Data adapter for Burgers viscous (SINDy nu=0.1). See configs/burgers_viscous.yaml.""" + +import os +import numpy as np +from scipy.io import loadmat + +_DATA_DIR = os.path.abspath(os.path.join( + os.path.dirname(__file__), '..', '..', 'pic', 'data', 'burgers' +)) + + +def load_data(): + burg = loadmat(os.path.join(_DATA_DIR, 'burgers.mat')) + t = np.ravel(burg['t']) + x = np.ravel(burg['x']) + data = np.transpose(np.real(burg['usol'])) + grids = np.meshgrid(t, x, indexing='ij') + return tuple(grids), data, ['u'], 1 diff --git a/projects/thesis/adapters/kdv.py b/projects/thesis/adapters/kdv.py new file mode 100644 index 00000000..607b1fd6 --- /dev/null +++ b/projects/thesis/adapters/kdv.py @@ -0,0 +1,18 @@ +"""Data adapter for KdV (SINDy benchmark). See configs/kdv.yaml.""" + +import os +import numpy as np +from scipy.io import loadmat + +_DATA_DIR = os.path.abspath(os.path.join( + os.path.dirname(__file__), '..', '..', 'pic', 'data', 'kdv' +)) + + +def load_data(): + d = loadmat(os.path.join(_DATA_DIR, 'kdv_sindy.mat')) + t = np.ravel(d['t']) + x = np.ravel(d['x']) + u = np.transpose(np.real(d['usol'])) + grids = np.meshgrid(t, x, indexing='ij') + return tuple(grids), u, ['u'], 1 diff --git a/projects/thesis/adapters/kdv_cossin.py b/projects/thesis/adapters/kdv_cossin.py new file mode 100644 index 00000000..85ee4b80 --- /dev/null +++ b/projects/thesis/adapters/kdv_cossin.py @@ -0,0 +1,35 @@ +"""Data adapter for KdV with cos(t)*sin(x) source. See configs/kdv_cossin.yaml.""" + +import os +import numpy as np + +from epde.interface.prepared_tokens import CustomTokens, CustomEvaluator + +_DATA_DIR = os.path.abspath(os.path.join( + os.path.dirname(__file__), '..', '..', 'pic', 'data', 'kdv' +)) + + +def load_data(): + shape = 80 + data = np.loadtxt(os.path.join(_DATA_DIR, 'data.csv'), delimiter=',').T + t = np.linspace(0, 1, shape + 1) + x = np.linspace(0, 1, shape + 1) + grids = np.meshgrid(t, x, indexing='ij') + return tuple(grids), data, ['u'], 1 + + +def build_extra_tokens(coords, dim): + custom_eval = { + 'cos(t)sin(x)': lambda *grids, **kwargs: (np.cos(grids[0]) * np.sin(grids[1])) ** kwargs['power'] + } + evaluator = CustomEvaluator(custom_eval, eval_fun_params_labels=['power']) + return [CustomTokens( + token_type='trigonometric', + token_labels=['cos(t)sin(x)'], + evaluator=evaluator, + params_ranges={'power': (1, 1)}, + params_equality_ranges={}, + meaningful=True, + unique_token_type=False, + )] diff --git a/projects/thesis/adapters/ks.py b/projects/thesis/adapters/ks.py new file mode 100644 index 00000000..7a83aae2 --- /dev/null +++ b/projects/thesis/adapters/ks.py @@ -0,0 +1,18 @@ +"""Data adapter for Kuramoto-Sivashinsky. See configs/ks.yaml.""" + +import os +import numpy as np +import scipy.io as scio + +_DATA_DIR = os.path.abspath(os.path.join( + os.path.dirname(__file__), '..', '..', 'pic', 'data', 'ks' +)) + + +def load_data(): + d = scio.loadmat(os.path.join(_DATA_DIR, 'kuramoto_sivishinky.mat')) + t = np.ravel(d['tt']) + x = np.ravel(d['x']) + u = d['uu'].T + grids = np.meshgrid(t, x, indexing='ij') + return tuple(grids), u, ['u'], 1 diff --git a/projects/thesis/adapters/lorenz.py b/projects/thesis/adapters/lorenz.py new file mode 100644 index 00000000..817e663a --- /dev/null +++ b/projects/thesis/adapters/lorenz.py @@ -0,0 +1,14 @@ +"""Data adapter for Lorenz system. See configs/lorenz.yaml.""" + +import os +import numpy as np + +_DATA_DIR = os.path.abspath(os.path.join( + os.path.dirname(__file__), '..', '..', 'pic', 'data', 'lorenz' +)) + + +def load_data(): + t = np.load(os.path.join(_DATA_DIR, 't.npy'))[:1000] + data = np.load(os.path.join(_DATA_DIR, 'lorenz.npy'))[:1000] + return (t,), [data[:, 0], data[:, 1], data[:, 2]], ['u', 'v', 'w'], 0 diff --git a/projects/thesis/adapters/lv.py b/projects/thesis/adapters/lv.py new file mode 100644 index 00000000..21a2745c --- /dev/null +++ b/projects/thesis/adapters/lv.py @@ -0,0 +1,14 @@ +"""Data adapter for Lotka-Volterra. See configs/lv.yaml.""" + +import os +import numpy as np + +_DATA_DIR = os.path.abspath(os.path.join( + os.path.dirname(__file__), '..', '..', 'pic', 'data', 'lv' +)) + + +def load_data(): + t = np.load(os.path.join(_DATA_DIR, 't_20.npy'))[:150] + data = np.load(os.path.join(_DATA_DIR, 'data_20.npy'))[:150] + return (t,), [data[:, 0], data[:, 1]], ['u', 'v'], 0 diff --git a/projects/thesis/adapters/ns.py b/projects/thesis/adapters/ns.py new file mode 100644 index 00000000..4a45284c --- /dev/null +++ b/projects/thesis/adapters/ns.py @@ -0,0 +1,13 @@ +"""Data adapter for Navier-Stokes (placeholder). + +NS is excluded from smoke runs. Truth equations + loader will be filled +in once the Re-specific ground-truth pair + continuity equation are +pinned down (mirror ``ns.py:ns_data`` on cylinder_nektar_wake.mat). +""" + + +def load_data(): + raise NotImplementedError( + 'NS data loader not implemented yet; mirror ns.py:ns_data on ' + 'cylinder_nektar_wake.mat once truth tokens are pinned.' + ) diff --git a/projects/thesis/adapters/ode.py b/projects/thesis/adapters/ode.py new file mode 100644 index 00000000..2e7db9fe --- /dev/null +++ b/projects/thesis/adapters/ode.py @@ -0,0 +1,21 @@ +"""Data adapter for Forced Damped Oscillator. See configs/ode.yaml.""" + +import os +import numpy as np + +from epde import TrigonometricTokens + +_DATA_DIR = os.path.abspath(os.path.join( + os.path.dirname(__file__), '..', '..', 'pic', 'data', 'ode' +)) + + +def load_data(): + step, n = 0.05, 320 + t = np.arange(0., step * n, step) + data = np.load(os.path.join(_DATA_DIR, 'ode_data.npy')) + return (t,), [data], ['u'], 0 + + +def build_extra_tokens(coords, dim): + return [TrigonometricTokens(freq=(2 - 1e-8, 2 + 1e-8), dimensionality=dim)] diff --git a/projects/thesis/adapters/pde_compound.py b/projects/thesis/adapters/pde_compound.py new file mode 100644 index 00000000..412c8799 --- /dev/null +++ b/projects/thesis/adapters/pde_compound.py @@ -0,0 +1,17 @@ +"""Data adapter for synthetic compound PDE. See configs/pde_compound.yaml.""" + +import os +import numpy as np + +_DATA_DIR = os.path.abspath(os.path.join( + os.path.dirname(__file__), '..', '..', 'pic', 'data', 'pde_compound' +)) + + +def load_data(): + data = np.load(os.path.join(_DATA_DIR, 'PDE_compound.npy')) + nx, nt = 100, 251 + x = np.linspace(1, 2, nx) + t = np.linspace(0, 0.5, nt) + grids = np.meshgrid(t, x, indexing='ij') + return tuple(grids), data, ['u'], 1 diff --git a/projects/thesis/adapters/pde_divide.py b/projects/thesis/adapters/pde_divide.py new file mode 100644 index 00000000..a439195c --- /dev/null +++ b/projects/thesis/adapters/pde_divide.py @@ -0,0 +1,17 @@ +"""Data adapter for synthetic rational PDE. See configs/pde_divide.yaml.""" + +import os +import numpy as np + +_DATA_DIR = os.path.abspath(os.path.join( + os.path.dirname(__file__), '..', '..', 'pic', 'data', 'pde_divide' +)) + + +def load_data(): + data = np.load(os.path.join(_DATA_DIR, 'PDE_divide.npy')) + nx, nt = 100, 251 + x = np.linspace(1, 2, nx) + t = np.linspace(0, 0.5, nt) + grids = np.meshgrid(t, x, indexing='ij') + return tuple(grids), data, ['u'], 1 diff --git a/projects/thesis/adapters/vdp.py b/projects/thesis/adapters/vdp.py new file mode 100644 index 00000000..7e8264bc --- /dev/null +++ b/projects/thesis/adapters/vdp.py @@ -0,0 +1,26 @@ +"""Data adapter for Van der Pol. See configs/vdp.yaml. + +build_extra_tokens supplies a tight TrigonometricTokens around freq=2 so the +search exposes ``sin(2t)`` as a factor even though the truth equation +doesn't actually use it (kept for parity with the LEGACY runner). +""" + +import os +import numpy as np + +from epde import TrigonometricTokens + +_DATA_DIR = os.path.abspath(os.path.join( + os.path.dirname(__file__), '..', '..', 'pic', 'data', 'vdp' +)) + + +def load_data(): + step, n = 0.05, 320 + t = np.arange(0., step * n, step) + data = np.load(os.path.join(_DATA_DIR, 'vdp_data.npy')) + return (t,), [data], ['u'], 0 + + +def build_extra_tokens(coords, dim): + return [TrigonometricTokens(freq=(2 - 1e-8, 2 + 1e-8), dimensionality=dim)] diff --git a/projects/thesis/adapters/wave.py b/projects/thesis/adapters/wave.py new file mode 100644 index 00000000..9d218da2 --- /dev/null +++ b/projects/thesis/adapters/wave.py @@ -0,0 +1,17 @@ +"""Data adapter for the 1+1D wave equation. See configs/wave.yaml.""" + +import os +import numpy as np + +_DATA_DIR = os.path.abspath(os.path.join( + os.path.dirname(__file__), '..', '..', 'pic', 'data', 'wave' +)) + + +def load_data(): + shape = 80 + data = np.loadtxt(os.path.join(_DATA_DIR, 'wave_sln_80.csv'), delimiter=',').T + t = np.linspace(0, 1, shape + 1) + x = np.linspace(0, 1, shape + 1) + grids = np.meshgrid(t, x, indexing='ij') + return tuple(grids), data, ['u'], 1 diff --git a/projects/thesis/configs/ac.yaml b/projects/thesis/configs/ac.yaml new file mode 100644 index 00000000..68c3ef85 --- /dev/null +++ b/projects/thesis/configs/ac.yaml @@ -0,0 +1,5 @@ +# Allen-Cahn (1+1D reaction-diffusion PDE) +# du/dx0 = 0.0001*d^2u/dx1^2 + 5*u - 5*u^3 +name: ac +truth_equations: + - "0.0001 * d^2u/dx1^2{power: 1.0} + -5.0 * u{power: 3.0} + 5.0 * u{power: 1.0} = du/dx0{power: 1.0}" diff --git a/projects/thesis/configs/burgers_inviscid.yaml b/projects/thesis/configs/burgers_inviscid.yaml new file mode 100644 index 00000000..529915af --- /dev/null +++ b/projects/thesis/configs/burgers_inviscid.yaml @@ -0,0 +1,6 @@ +# Burgers inviscid (1+1D PDE) +# du/dx0 = -u * du/dx1 +# Data: burgers_sln_100.csv (same CSV used by ``burgers_discovery``). +name: burgers_inviscid +truth_equations: + - "-1.0 * u{power: 1.0} * du/dx1{power: 1.0} = du/dx0{power: 1.0}" diff --git a/projects/thesis/configs/burgers_viscous.yaml b/projects/thesis/configs/burgers_viscous.yaml new file mode 100644 index 00000000..91373c8b --- /dev/null +++ b/projects/thesis/configs/burgers_viscous.yaml @@ -0,0 +1,6 @@ +# Burgers viscous (1+1D PDE; SINDy benchmark, nu=0.1) +# du/dx0 = -u*du/dx1 + 0.1*d^2u/dx1^2 +# Data: burgers.mat +name: burgers_viscous +truth_equations: + - "-1.0 * u{power: 1.0} * du/dx1{power: 1.0} + 0.1 * d^2u/dx1^2{power: 1.0} = du/dx0{power: 1.0}" diff --git a/projects/thesis/configs/kdv.yaml b/projects/thesis/configs/kdv.yaml new file mode 100644 index 00000000..65ce3e38 --- /dev/null +++ b/projects/thesis/configs/kdv.yaml @@ -0,0 +1,6 @@ +# KdV homogeneous (1+1D PDE) +# du/dx0 = -6*u*du/dx1 - d^3u/dx1^3 +# Data: kdv_sindy.mat +name: kdv +truth_equations: + - "-6.0 * du/dx1{power: 1.0} * u{power: 1.0} + -1.0 * d^3u/dx1^3{power: 1.0} = du/dx0{power: 1.0}" diff --git a/projects/thesis/configs/kdv_cossin.yaml b/projects/thesis/configs/kdv_cossin.yaml new file mode 100644 index 00000000..47943cb0 --- /dev/null +++ b/projects/thesis/configs/kdv_cossin.yaml @@ -0,0 +1,6 @@ +# KdV with cos(t)*sin(x) source term (1+1D PDE) +# du/dx0 = -6*u*du/dx1 - d^3u/dx1^3 + cos(t)*sin(x) +# Data: data.csv (loaded via np.loadtxt) +name: kdv_cossin +truth_equations: + - "-6.0 * du/dx1{power: 1.0} * u{power: 1.0} + -1.0 * d^3u/dx1^3{power: 1.0} + 1.0 * cos(t)sin(x){power: 1.0} = du/dx0{power: 1.0}" diff --git a/projects/thesis/configs/ks.yaml b/projects/thesis/configs/ks.yaml new file mode 100644 index 00000000..6b058789 --- /dev/null +++ b/projects/thesis/configs/ks.yaml @@ -0,0 +1,6 @@ +# Kuramoto-Sivashinsky (1+1D PDE) +# du/dx0 = -u*du/dx1 - d^2u/dx1^2 - d^4u/dx1^4 +# Data: kuramoto_sivishinky.mat +name: ks +truth_equations: + - "-1.0 * u{power: 1.0} * du/dx1{power: 1.0} + -1.0 * d^2u/dx1^2{power: 1.0} + -1.0 * d^4u/dx1^4{power: 1.0} = du/dx0{power: 1.0}" diff --git a/projects/thesis/configs/lorenz.yaml b/projects/thesis/configs/lorenz.yaml new file mode 100644 index 00000000..a3b84933 --- /dev/null +++ b/projects/thesis/configs/lorenz.yaml @@ -0,0 +1,9 @@ +# Lorenz system (coupled 3D ODE) +# du/dx0 = 10*v - 10*u +# dv/dx0 = 28*u - u*w - v +# dw/dx0 = u*v - (8/3)*w +name: lorenz +truth_equations: + - "10.0 * v{power: 1.0} + -10.0 * u{power: 1.0} = du/dx0{power: 1.0}" + - "28.0 * u{power: 1.0} + -1.0 * u{power: 1.0} * w{power: 1.0} + -1.0 * v{power: 1.0} = dv/dx0{power: 1.0}" + - "1.0 * u{power: 1.0} * v{power: 1.0} + -2.6666666666666665 * w{power: 1.0} = dw/dx0{power: 1.0}" diff --git a/projects/thesis/configs/lv.yaml b/projects/thesis/configs/lv.yaml new file mode 100644 index 00000000..2ecca667 --- /dev/null +++ b/projects/thesis/configs/lv.yaml @@ -0,0 +1,6 @@ +# Lotka-Volterra (coupled 2D ODE) +# Generated with alpha=2/3, beta=4/3, delta=1, gamma=1. +name: lv +truth_equations: + - "0.6666666666666666 * u{power: 1.0} + -1.3333333333333333 * u{power: 1.0} * v{power: 1.0} = du/dx0{power: 1.0}" + - "1.0 * u{power: 1.0} * v{power: 1.0} + -1.0 * v{power: 1.0} = dv/dx0{power: 1.0}" diff --git a/projects/thesis/configs/ns.yaml b/projects/thesis/configs/ns.yaml new file mode 100644 index 00000000..4826b96f --- /dev/null +++ b/projects/thesis/configs/ns.yaml @@ -0,0 +1,7 @@ +# Navier-Stokes (2+1D, coupled PDE system) +# +# NOTE: excluded from smoke runs -- runtime is much higher than the rest +# of the benchmark. Truth equations are TODO until the Re-specific +# ground-truth pair + continuity is decided. +name: ns +truth_equations: [] diff --git a/projects/thesis/configs/ode.yaml b/projects/thesis/configs/ode.yaml new file mode 100644 index 00000000..23562d1e --- /dev/null +++ b/projects/thesis/configs/ode.yaml @@ -0,0 +1,6 @@ +# Forced Damped Oscillator (scalar ODE) +# u'' + sin(2t)*u' + 4*u = 1.5*t +# i.e. d^2u/dx0^2 = -4*u - sin(2t)*du/dx0 + 1.5*t +name: ode +truth_equations: + - "-4.0 * u{power: 1.0} + -1.0 * du/dx0{power: 1.0} * sin{power: 1.0, freq: 2.0, dim: 0.0} + 1.5 * x_0{power: 1.0, dim: 0.0} = d^2u/dx0^2{power: 1.0}" diff --git a/projects/thesis/configs/pde_compound.yaml b/projects/thesis/configs/pde_compound.yaml new file mode 100644 index 00000000..3157d34b --- /dev/null +++ b/projects/thesis/configs/pde_compound.yaml @@ -0,0 +1,6 @@ +# Compound PDE (synthetic 1+1D PDE) +# du/dx0 = (du/dx1)^2 + d^2u/dx1^2 * u +# Data: PDE_compound.npy +name: pde_compound +truth_equations: + - "1.0 * du/dx1{power: 2.0} + 1.0 * d^2u/dx1^2{power: 1.0} * u{power: 1.0} = du/dx0{power: 1.0}" diff --git a/projects/thesis/configs/pde_divide.yaml b/projects/thesis/configs/pde_divide.yaml new file mode 100644 index 00000000..512a9ae6 --- /dev/null +++ b/projects/thesis/configs/pde_divide.yaml @@ -0,0 +1,6 @@ +# Rational PDE (synthetic 1+1D PDE) +# du/dx0 * x = du/dx1 + 0.25 * d^2u/dx1^2 * x +# Data: PDE_divide.npy +name: pde_divide +truth_equations: + - "1.0 * du/dx1{power: 1.0} + 0.25 * d^2u/dx1^2{power: 1.0} * x_1{power: 1.0, dim: 1.0} = du/dx0{power: 1.0} * x_1{power: 1.0, dim: 1.0}" diff --git a/projects/thesis/configs/vdp.yaml b/projects/thesis/configs/vdp.yaml new file mode 100644 index 00000000..aba81225 --- /dev/null +++ b/projects/thesis/configs/vdp.yaml @@ -0,0 +1,6 @@ +# Van der Pol oscillator (ODE) +# u'' + 0.2*(u^2 - 1)*u' + u = 0 +# i.e. d^2u/dx0^2 = -0.2*u^2*du/dx0 + 0.2*du/dx0 - u +name: vdp +truth_equations: + - "-0.2 * u{power: 2.0} * du/dx0{power: 1.0} + 0.2 * du/dx0{power: 1.0} + -1.0 * u{power: 1.0} + -0.0 = d^2u/dx0^2{power: 1.0}" diff --git a/projects/thesis/configs/wave.yaml b/projects/thesis/configs/wave.yaml new file mode 100644 index 00000000..ccbbbb14 --- /dev/null +++ b/projects/thesis/configs/wave.yaml @@ -0,0 +1,6 @@ +# Wave equation (1+1D PDE; wave speed^2 = 0.04, c = 0.2) +# d^2u/dx0^2 = 0.04 * d^2u/dx1^2 +# Data: wave_sln_80.csv +name: wave +truth_equations: + - "0.04 * d^2u/dx1^2{power: 1.0} = d^2u/dx0^2{power: 1.0}" diff --git a/projects/thesis/run.py b/projects/thesis/run.py new file mode 100644 index 00000000..2f52e4c5 --- /dev/null +++ b/projects/thesis/run.py @@ -0,0 +1,92 @@ +"""Unified CLI entry for the thesis Section 4.5 main comparison. + +Usage: + python projects/thesis/run.py [--reps N] [--pipelines legacy new] + [--outdir TAG] [--no-resume] + [--seed-base N] + +```` matches one of the YAML files in ``projects/thesis/configs/`` +(e.g. ``lv``, ``lorenz``, ``kdv``). The default pipelines are ``legacy`` and +``new``; pass ``--pipelines`` to override (the eight valid labels are +``legacy``, ``new``, and the six off-diagonal ablation labels -- see +``thesis_runner._PIPELINE_SETTINGS``). + +``--outdir TAG`` redirects results to ``results/TAG//`` so a tagged +sweep across multiple systems stays grouped under one folder. ``--outdir +/abs/path`` lands there directly. +""" + +from __future__ import annotations + +import argparse +import os +import sys + +_THIS_DIR = os.path.dirname(os.path.abspath(__file__)) +if _THIS_DIR not in sys.path: + sys.path.insert(0, _THIS_DIR) + +from thesis_runner import ( # noqa: E402 + ABLATION_PIPELINES, + CONFIGS_DIR, + PIPELINES, + _PIPELINE_SETTINGS, + load_config, + run_smoke, +) + + +def _available_systems() -> list: + if not os.path.isdir(CONFIGS_DIR): + return [] + return sorted( + os.path.splitext(f)[0] + for f in os.listdir(CONFIGS_DIR) + if f.endswith('.yaml') + ) + + +def main(argv=None) -> int: + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + parser.add_argument( + 'system', + help=f"system name (looked up as configs/.yaml). " + f"Available: {', '.join(_available_systems()) or '(none yet)'}", + ) + parser.add_argument('--reps', type=int, default=30, + help="reps per pipeline (default: 30)") + parser.add_argument( + '--pipelines', nargs='+', default=list(PIPELINES), + choices=tuple(_PIPELINE_SETTINGS), + help=f"pipeline labels (default: {' '.join(PIPELINES)})", + ) + parser.add_argument('--outdir', default=None, + help="results tag (lands at results///) " + "or absolute path; default reuses cfg's outdir") + parser.add_argument('--no-resume', dest='resume', action='store_false', default=True, + help="overwrite existing per-rep JSONs instead of skipping") + parser.add_argument('--seed-base', type=int, default=0, + help="seed for rep 0 (rep i uses seed_base + i)") + args = parser.parse_args(argv) + + try: + cfg = load_config(args.system) + except FileNotFoundError as exc: + parser.error(str(exc)) + + run_smoke( + cfg, + reps=args.reps, + pipelines=tuple(args.pipelines), + seed_base=args.seed_base, + resume=args.resume, + outdir=args.outdir, + ) + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/projects/thesis/run_ablation.py b/projects/thesis/run_ablation.py new file mode 100644 index 00000000..a2cd29bd --- /dev/null +++ b/projects/thesis/run_ablation.py @@ -0,0 +1,32 @@ +"""Ablation entry point: same CLI as ``run.py`` but defaults to the six +off-diagonal cells of the 2x2x2 factorial (``wape``, ``instab``, ``reg``, +``wape_instab``, ``wape_reg``, ``instab_reg``). + +The 000 (``legacy``) and 111 (``new``) corners are *not* run here -- they +are produced by ``run.py`` and the aggregator reads both label sets from +the same results tree. + +Usage: + python projects/thesis/run_ablation.py [--reps N] + [--outdir TAG] + [--no-resume] +""" + +from __future__ import annotations + +import sys + +from run import main as _main # noqa: E402 +from thesis_runner import ABLATION_PIPELINES # noqa: E402 + + +def main(argv=None) -> int: + argv = list(sys.argv[1:] if argv is None else argv) + # Inject the ablation pipelines unless the caller passed --pipelines explicitly. + if not any(a == '--pipelines' or a.startswith('--pipelines=') for a in argv): + argv += ['--pipelines', *ABLATION_PIPELINES] + return _main(argv) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/projects/thesis/thesis_ablation_aggregate.py b/projects/thesis/thesis_ablation_aggregate.py new file mode 100644 index 00000000..b6b0c352 --- /dev/null +++ b/projects/thesis/thesis_ablation_aggregate.py @@ -0,0 +1,221 @@ +""" +Aggregator for the thesis Section 4.5 ablation study (2x2x2 factorial). + +Walks every ``projects/thesis/results//_rep.json`` file +whose ``pipeline`` field names one of the 8 ablation cells, groups by +(system, cell), and writes a markdown summary plus a JSON snapshot. + +If your ablation runs landed under a tag (``--outdir ablation_v2`` -> +``results/ablation_v2//``), point ``--root`` at that subtree. +Either way the layout is always ``//*.json``; the 000 +(``legacy``) and 111 (``new``) corners are read from the same JSON files +that ``thesis_aggregate.py`` already consumes. + +Cell-label semantics (each label lists the NEW components that are ON): + + legacy 000 fitness=L2, sparsity=LASSO, use_pic=False + wape 100 fitness=L2LR, sparsity=LASSO, use_pic=False + instab 010 fitness=L2, sparsity=LASSO, use_pic=True + reg 001 fitness=L2, sparsity=VWSR, use_pic=False + wape_instab 110 fitness=L2LR, sparsity=LASSO, use_pic=True + wape_reg 101 fitness=L2LR, sparsity=VWSR, use_pic=False + instab_reg 011 fitness=L2, sparsity=VWSR, use_pic=True + new 111 fitness=L2LR, sparsity=VWSR, use_pic=True +""" + +from __future__ import annotations + +import argparse +import glob +import json +import os +import statistics +import sys +from collections import defaultdict + +_THIS_DIR = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, _THIS_DIR) +from thesis_metrics import consistency_rate, wilson_ci # noqa: E402 + + +# Ordered so the report reads from "all off" to "all on" along each axis. +ABLATION_CELLS = ( + 'legacy', + 'wape', 'instab', 'reg', + 'wape_instab', 'wape_reg', 'instab_reg', + 'new', +) + +DEFAULT_RESULTS_DIR = os.path.join(_THIS_DIR, 'results') + + +def _load_records(root: str): + records = defaultdict(lambda: defaultdict(list)) # records[system][cell] -> list + pattern = os.path.join(root, '*', '*.json') + for path in sorted(glob.glob(pattern)): + try: + with open(path, 'r', encoding='utf-8') as fh: + rec = json.load(fh) + except (OSError, json.JSONDecodeError): + continue + system = rec.get('system') or os.path.basename(os.path.dirname(path)) + cell = rec.get('pipeline') + if cell not in ABLATION_CELLS: + continue + records[system][cell].append(rec) + return records + + +def _summarize_cell(reps: list) -> dict: + if not reps: + return {'n': 0} + successes = sum(1 for r in reps if r.get('structural_success')) + hammings = [r['hamming'] for r in reps if r.get('hamming') is not None] + runtimes = [r['runtime_sec'] for r in reps if 'runtime_sec' in r] + discovered_tokens = [json.dumps(r.get('discovered_tokens', []), sort_keys=True) for r in reps] + rate = successes / len(reps) + ci = wilson_ci(successes, len(reps)) + mean_h = statistics.fmean(hammings) if hammings else float('nan') + mean_t = statistics.fmean(runtimes) if runtimes else float('nan') + errors = sum(1 for r in reps if 'error' in r) + return { + 'n': len(reps), + 'successes': successes, + 'rate': rate, + 'wilson_lo': ci[0], + 'wilson_hi': ci[1], + 'mean_hamming': mean_h, + 'consistency': consistency_rate(discovered_tokens), + 'mean_runtime_sec': mean_t, + 'errors': errors, + } + + +def _cell_axes(cell: str) -> tuple: + """Return ``(wape_on, instab_on, reg_on)`` triple for a given cell label.""" + if cell == 'legacy': + return (False, False, False) + if cell == 'new': + return (True, True, True) + parts = set(cell.split('_')) + return ('wape' in parts, 'instab' in parts, 'reg' in parts) + + +def _format_table(summary: dict) -> str: + header = ( + '| System | Cell | W | I | R | n | Success | mean H | cons | runtime |' + ) + sep = '|---|---|---|---|---|---|---|---|---|---|' + rows = [header, sep] + + def _check(b: bool) -> str: + return 'X' if b else '.' + + def _success(c): + if c['n'] == 0: + return '-' + return ( + f"{c['rate']*100:.0f}% [{c['wilson_lo']*100:.0f}-{c['wilson_hi']*100:.0f}%] " + f"({c['successes']}/{c['n']})" + ) + + def _num(c, key, fmt): + if c['n'] == 0: + return '-' + v = c.get(key) + if v is None or (isinstance(v, float) and v != v): + return '-' + return fmt.format(v) + + for system in sorted(summary.keys()): + for cell in ABLATION_CELLS: + c = summary[system].get(cell, {'n': 0}) + w, i, r = _cell_axes(cell) + rows.append( + f"| {system} | {cell} | {_check(w)} | {_check(i)} | {_check(r)} | " + f"{c['n']} | {_success(c)} | " + f"{_num(c, 'mean_hamming', '{:.1f}')} | " + f"{_num(c, 'consistency', '{:.2f}')} | " + f"{_num(c, 'mean_runtime_sec', '{:.1f}')}s |" + ) + return '\n'.join(rows) + + +def _format_contributions(summary: dict) -> str: + """Render the marginal contribution of each axis per system.""" + axes = ( + ('WAPE', 0, [('legacy', 'wape'), ('instab', 'wape_instab'), + ('reg', 'wape_reg'), ('instab_reg', 'new')]), + ('Instab', 1, [('legacy', 'instab'), ('wape', 'wape_instab'), + ('reg', 'instab_reg'), ('wape_reg', 'new')]), + ('Reg', 2, [('legacy', 'reg'), ('wape', 'wape_reg'), + ('instab', 'instab_reg'), ('wape_instab', 'new')]), + ) + rows = ['| System | Axis | mean delta success | mean delta H | n pairs |', + '|---|---|---|---|---|'] + for system in sorted(summary.keys()): + for axis_name, _idx, pairs in axes: + d_rate = [] + d_h = [] + for off_cell, on_cell in pairs: + off = summary[system].get(off_cell, {'n': 0}) + on = summary[system].get(on_cell, {'n': 0}) + if off['n'] == 0 or on['n'] == 0: + continue + d_rate.append(on['rate'] - off['rate']) + if ( + on.get('mean_hamming') is not None + and off.get('mean_hamming') is not None + and on['mean_hamming'] == on['mean_hamming'] + and off['mean_hamming'] == off['mean_hamming'] + ): + d_h.append(on['mean_hamming'] - off['mean_hamming']) + if not d_rate: + rows.append(f"| {system} | {axis_name} | - | - | 0 |") + continue + mean_dr = statistics.fmean(d_rate) + mean_dh = statistics.fmean(d_h) if d_h else float('nan') + dh_str = f"{mean_dh:+.2f}" if mean_dh == mean_dh else '-' + rows.append( + f"| {system} | {axis_name} | {mean_dr*100:+.1f}pp | " + f"{dh_str} | {len(d_rate)} |" + ) + return '\n'.join(rows) + + +def aggregate(root: str = None) -> dict: + root = root or DEFAULT_RESULTS_DIR + records = _load_records(root) + summary = { + system: {cell: _summarize_cell(reps) for cell, reps in by_cell.items()} + for system, by_cell in records.items() + } + return summary + + +def main(argv=None) -> int: + parser = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument('--root', default=DEFAULT_RESULTS_DIR, + help=f"results root to scan (default: {DEFAULT_RESULTS_DIR})") + parser.add_argument('--out', default=None, + help="path for the JSON snapshot (default: /thesis_ablation_summary.json)") + args = parser.parse_args(argv) + + summary = aggregate(args.root) + print('# Thesis Section 4.5 -- Ablation Cells') + print() + print(_format_table(summary)) + print() + print('# Marginal contribution per axis (mean delta across the 4 mutually-exclusive pairs)') + print() + print(_format_contributions(summary)) + out_path = args.out or os.path.join(_THIS_DIR, 'thesis_ablation_summary.json') + with open(out_path, 'w', encoding='utf-8') as fh: + json.dump(summary, fh, indent=2) + print(f"\nWrote {out_path}") + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/projects/thesis/thesis_aggregate.py b/projects/thesis/thesis_aggregate.py new file mode 100644 index 00000000..2384981f --- /dev/null +++ b/projects/thesis/thesis_aggregate.py @@ -0,0 +1,147 @@ +""" +Aggregator for thesis Section 4.5 smoke / full-run results. + +Walks every ``projects/thesis/results//_rep.json`` +file, groups by (system, pipeline), and writes a markdown summary plus a +JSON snapshot. Metrics per (system, pipeline) cell: + + - structural_success_rate (with Wilson 95% CI) + - mean Hamming distance + - consistency_rate (modal-set agreement) + - mean runtime + +Pass ``--root`` to point at a tagged results tree (e.g. +``projects/thesis/results/ablation_v2``) -- the layout is always +``//*.json``. +""" + +from __future__ import annotations + +import argparse +import glob +import json +import os +import statistics +import sys +from collections import defaultdict + +_THIS_DIR = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, _THIS_DIR) +from thesis_metrics import consistency_rate, wilson_ci # noqa: E402 + + +PIPELINES = ('legacy', 'new') +DEFAULT_RESULTS_DIR = os.path.join(_THIS_DIR, 'results') + + +def _load_records(root: str): + records = defaultdict(lambda: defaultdict(list)) # records[system][pipeline] -> list + pattern = os.path.join(root, '*', '*.json') + for path in sorted(glob.glob(pattern)): + try: + with open(path, 'r', encoding='utf-8') as fh: + rec = json.load(fh) + except (OSError, json.JSONDecodeError): + continue + system = rec.get('system') or os.path.basename(os.path.dirname(path)) + pipeline = rec.get('pipeline') + if pipeline not in PIPELINES: + continue + records[system][pipeline].append(rec) + return records + + +def _summarize_cell(reps: list) -> dict: + if not reps: + return {'n': 0} + successes = sum(1 for r in reps if r.get('structural_success')) + hammings = [r['hamming'] for r in reps if r.get('hamming') is not None] + runtimes = [r['runtime_sec'] for r in reps if 'runtime_sec' in r] + discovered_tokens = [json.dumps(r.get('discovered_tokens', []), sort_keys=True) for r in reps] + rate = successes / len(reps) + ci = wilson_ci(successes, len(reps)) + mean_h = statistics.fmean(hammings) if hammings else float('nan') + mean_t = statistics.fmean(runtimes) if runtimes else float('nan') + errors = sum(1 for r in reps if 'error' in r) + return { + 'n': len(reps), + 'successes': successes, + 'rate': rate, + 'wilson_lo': ci[0], + 'wilson_hi': ci[1], + 'mean_hamming': mean_h, + 'consistency': consistency_rate(discovered_tokens), + 'mean_runtime_sec': mean_t, + 'errors': errors, + } + + +def _format_table(summary: dict) -> str: + header = ( + '| System | n | Legacy success | Legacy H | Legacy cons | ' + 'NEW success | NEW H | NEW cons | runtime (L / N) |' + ) + sep = '|---|---|---|---|---|---|---|---|---|' + rows = [header, sep] + for system in sorted(summary.keys()): + legacy = summary[system].get('legacy', {'n': 0}) + new = summary[system].get('new', {'n': 0}) + + def cell_success(c): + if c['n'] == 0: + return '-' + return ( + f"{c['rate']*100:.0f}% [{c['wilson_lo']*100:.0f}-{c['wilson_hi']*100:.0f}%] " + f"({c['successes']}/{c['n']})" + ) + + def cell_num(c, key, fmt): + if c['n'] == 0: + return '-' + v = c.get(key) + if v is None or (isinstance(v, float) and v != v): + return '-' + return fmt.format(v) + + rows.append( + f"| {system} | {max(legacy['n'], new['n'])} | " + f"{cell_success(legacy)} | {cell_num(legacy, 'mean_hamming', '{:.1f}')} | " + f"{cell_num(legacy, 'consistency', '{:.2f}')} | " + f"{cell_success(new)} | {cell_num(new, 'mean_hamming', '{:.1f}')} | " + f"{cell_num(new, 'consistency', '{:.2f}')} | " + f"{cell_num(legacy, 'mean_runtime_sec', '{:.1f}')}s / " + f"{cell_num(new, 'mean_runtime_sec', '{:.1f}')}s |" + ) + return '\n'.join(rows) + + +def aggregate(root: str = None) -> dict: + root = root or DEFAULT_RESULTS_DIR + records = _load_records(root) + summary = { + system: {pipeline: _summarize_cell(reps) for pipeline, reps in by_pipeline.items()} + for system, by_pipeline in records.items() + } + return summary + + +def main(argv=None) -> int: + parser = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument('--root', default=DEFAULT_RESULTS_DIR, + help=f"results root to scan (default: {DEFAULT_RESULTS_DIR})") + parser.add_argument('--out', default=None, + help="path for the JSON snapshot (default: /../thesis_summary.json)") + args = parser.parse_args(argv) + + summary = aggregate(args.root) + print(_format_table(summary)) + out_path = args.out or os.path.join(_THIS_DIR, 'thesis_summary.json') + with open(out_path, 'w', encoding='utf-8') as fh: + json.dump(summary, fh, indent=2) + print(f"\nWrote {out_path}") + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/projects/thesis/thesis_metrics.py b/projects/thesis/thesis_metrics.py new file mode 100644 index 00000000..dcd13221 --- /dev/null +++ b/projects/thesis/thesis_metrics.py @@ -0,0 +1,221 @@ +""" +Structural metrics for the thesis Section 4.5 EPDE comparison. + +The metric pipeline is text-based: equations are read as strings (the +form produced by EPDE's :meth:`equations(only_str=True)`), parsed into a +canonical token representation that ignores coefficient values and term +ordering, then compared via Hamming distance / equality / modal-set +agreement across repetitions. +""" + +from __future__ import annotations + +import math +import re +from collections import Counter +from typing import Iterable, List, Sequence + +# Factor pattern: ``name{key1: val1, key2: val2, ...}`` where ``name`` can +# contain letters, digits, and the symbol characters EPDE uses for +# derivative tokens (``d``, ``u``, ``/``, ``^``, digits) and trig product +# tokens (e.g. ``cos(t)sin(x)``). +_FACTOR_RE = re.compile(r'([A-Za-z0-9_\^/\(\)]+)\s*\{([^}]*)\}') +_PARAM_RE = re.compile(r'([A-Za-z_][A-Za-z0-9_]*)\s*:\s*([^,]+)') +_PARAM_ROUND_DIGITS = 3 + + +def _round_param(value: str): + value = value.strip() + try: + return round(float(value), _PARAM_ROUND_DIGITS) + except ValueError: + return value + + +def _parse_factor(text: str): + """Return ``(name, frozenset_of_param_items)`` or None if no factor.""" + m = _FACTOR_RE.search(text) + if m is None: + return None + name = m.group(1) + params_str = m.group(2) + params = {} + for pm in _PARAM_RE.finditer(params_str): + params[pm.group(1)] = _round_param(pm.group(2)) + return (name, frozenset(params.items())) + + +def _parse_term(term_text: str): + """Parse a single ``c * f1{...} * f2{...}`` term into a frozenset of factors. + + Pure-constant terms (e.g. ``0.0``) and terms whose leading coefficient + is numerically zero are filtered out by returning None. + """ + pieces = [p.strip() for p in term_text.split('*')] + factors = [] + coef = 1.0 + coef_seen = False + for piece in pieces: + if not piece: + continue + factor = _parse_factor(piece) + if factor is None: + # piece is a bare numeric coefficient (or unparseable scalar). + try: + val = float(piece) + coef *= val + coef_seen = True + continue + except ValueError: + # Unrecognised piece: skip rather than crash; the canonical + # set will simply omit it (and Hamming will reflect that). + continue + factors.append(factor) + + if not factors: + # Pure-constant or unparseable term -> drop. + return None + if coef_seen and abs(coef) < 1e-12: + # Zero coefficient -> term doesn't actually appear in the equation. + return None + return frozenset(factors) + + +def _canonical_equation(eq_text: str): + """Parse one equation ``rhs_sum = target`` into a canonical tuple. + + Returns ``(target_term, frozenset_of_rhs_terms)`` or None if no ``=``. + """ + if '=' not in eq_text: + return None + left, right = eq_text.split('=', 1) + target_term = _parse_term(right) + rhs_terms = [] + for term_text in left.split('+'): + term = _parse_term(term_text) + if term is not None: + rhs_terms.append(term) + return (target_term, frozenset(rhs_terms)) + + +def canonical_tokens(eq_texts: Sequence[str]) -> frozenset: + """Convert a list of equation text strings into a canonical structure. + + Each equation contributes one element to the returned frozenset: + ``(target_term, frozenset_of_rhs_terms)``. The result ignores + coefficient magnitudes, term ordering, and factor ordering within + terms; it preserves factor names + parameters (powers, freqs, dims) + rounded to :data:`_PARAM_ROUND_DIGITS` digits. + """ + out = [] + for eq in eq_texts: + if not eq.strip(): + continue + canon = _canonical_equation(eq) + if canon is not None: + out.append(canon) + return frozenset(out) + + +def hamming(discovered: frozenset, truth: frozenset) -> int: + """Term-level structural distance between two canonical equation systems. + + Equations are matched by their target (LHS) term. For each matched + target, the contribution is the cardinality of the symmetric + difference of the right-hand-side term sets — so a single missing or + extra rhs term costs 1. For equations whose target exists in only + one side, the cost is ``1 + len(rhs)`` (target mismatch plus all its + rhs terms). A pure-constant (`+ 0.0`) term is filtered out at + canonicalisation time and never contributes. + + Examples (Lorenz first equation only): + truth = {(du/dt, {a, b, c})}, discovered = {(du/dt, {a, b})} + -> hamming = 1 (one rhs term missing) + truth = {(du/dt, {a, b})}, discovered = {(dv/dt, {a, b})} + -> hamming = 1 + 2 + 1 + 2 = 6 (target differs, both sides counted) + """ + truth_by_target = {target: rhs for target, rhs in truth} + disc_by_target = {target: rhs for target, rhs in discovered} + + total = 0 + for target in set(truth_by_target) | set(disc_by_target): + truth_rhs = truth_by_target.get(target) + disc_rhs = disc_by_target.get(target) + if truth_rhs is None: + total += 1 + len(disc_rhs) + elif disc_rhs is None: + total += 1 + len(truth_rhs) + else: + total += len(truth_rhs.symmetric_difference(disc_rhs)) + return total + + +def structural_success(discovered: frozenset, truth: frozenset) -> bool: + """True iff ``discovered`` equals ``truth`` as a canonical system.""" + return hamming(discovered, truth) == 0 + + +def consistency_rate(reps_canonical: Iterable[frozenset]) -> float: + """Fraction of reps whose canonical system equals the modal canonical system.""" + reps = list(reps_canonical) + if not reps: + return 0.0 + counts = Counter(reps) + modal_count = counts.most_common(1)[0][1] + return modal_count / len(reps) + + +def wilson_ci(successes: int, n: int, z: float = 1.96): + """Wilson 95% CI for a binomial proportion.""" + if n == 0: + return (0.0, 0.0) + p = successes / n + denom = 1.0 + z * z / n + center = (p + z * z / (2 * n)) / denom + half = z * math.sqrt(p * (1 - p) / n + z * z / (4 * n * n)) / denom + return (max(0.0, center - half), min(1.0, center + half)) + + +if __name__ == '__main__': + # Quick self-check: round-trip the Lorenz triple and confirm Hamming == 0 + # against itself, then perturb one term and confirm Hamming == 2. + lorenz_truth = [ + '10.0 * v{power: 1.0} + -10.0 * u{power: 1.0} = du/dx0{power: 1.0}', + '28.0 * u{power: 1.0} + -1.0 * u{power: 1.0} * w{power: 1.0} + -1.0 * v{power: 1.0} = dv/dx0{power: 1.0}', + '1.0 * u{power: 1.0} * v{power: 1.0} + -2.6666666666666665 * w{power: 1.0} = dw/dx0{power: 1.0}', + ] + canon_truth = canonical_tokens(lorenz_truth) + print('canon_truth size:', len(canon_truth)) + assert hamming(canon_truth, canon_truth) == 0 + assert structural_success(canon_truth, canon_truth) + + perturbed = list(lorenz_truth) + # Drop the -10*u term from the first equation -> one rhs term missing. + perturbed[0] = '10.0 * v{power: 1.0} = du/dx0{power: 1.0}' + canon_perturbed = canonical_tokens(perturbed) + h = hamming(canon_perturbed, canon_truth) + print('hamming(1 term missing) =', h) + assert h == 1, f"expected 1, got {h}" + + # Swap one rhs term for a different one: 1 removed + 1 added = 2. + perturbed2 = list(lorenz_truth) + perturbed2[0] = ('10.0 * v{power: 1.0} + -10.0 * u{power: 2.0} ' + '= du/dx0{power: 1.0}') + h2 = hamming(canonical_tokens(perturbed2), canon_truth) + print('hamming(1 term swapped) =', h2) + assert h2 == 2, f"expected 2, got {h2}" + + # Adding a pure-constant `+ 0.0` term must NOT change the canonical form. + with_zero = list(lorenz_truth) + with_zero[0] = '10.0 * v{power: 1.0} + -10.0 * u{power: 1.0} + 0.0 = du/dx0{power: 1.0}' + h_zero = hamming(canonical_tokens(with_zero), canon_truth) + print('hamming(+0.0 added) =', h_zero) + assert h_zero == 0, f"expected 0, got {h_zero}" + + # Drop a whole equation -> target + its 2 rhs terms = 3. + perturbed3 = list(lorenz_truth[:2]) + h3 = hamming(canonical_tokens(perturbed3), canon_truth) + print('hamming(1 equation missing) =', h3) + assert h3 == 3, f"expected 3, got {h3}" + + print('thesis_metrics self-check OK') diff --git a/projects/thesis/thesis_runner.py b/projects/thesis/thesis_runner.py new file mode 100644 index 00000000..6c8a4d65 --- /dev/null +++ b/projects/thesis/thesis_runner.py @@ -0,0 +1,522 @@ +""" +Shared runner module for the thesis Section 4.5 within-platform comparison. + +This module is the single source of truth for: + * the 8-cell pipeline table (``_PIPELINE_SETTINGS`` -> ``pipeline_settings``) + * the ``SystemCfg`` dataclass consumed by ``build_search`` / ``run_one`` + * ``run_smoke`` (batched per-rep JSON dumps with resume-on-restart) + * ``load_config`` for parsing per-system YAML configs + +Per-system configuration lives at: + + projects/thesis/configs/.yaml (declarative: truth equations, + output dir, data_fun_pow, ...) + projects/thesis/adapters/.py (Python: load_data(), + build_extra_tokens(coords, dim)) + +Pipeline selection (``legacy`` vs ``new`` is the main thesis comparison; the +six ablation cells off the 000/111 diagonal cover the 2x2x2 factorial): + + LEGACY -> L2Fitness + LASSOSparsity + use_pic=False + NEW -> L2LRFitness + VWSRSparsity + use_pic=True +""" + +from __future__ import annotations + +import importlib +import json +import os +import sys +import time +import traceback +from dataclasses import dataclass, field +from typing import Any, Callable, Iterable, Optional + +import numpy as np +import torch + +# Make sure the EPDE package is importable when running this module's CLI +# entries directly (``python projects/thesis/run.py lv``). +_THIS_DIR = os.path.dirname(os.path.abspath(__file__)) +_REPO_ROOT = os.path.abspath(os.path.join(_THIS_DIR, '..', '..')) +if _REPO_ROOT not in sys.path: + sys.path.insert(0, _REPO_ROOT) +if _THIS_DIR not in sys.path: + sys.path.insert(0, _THIS_DIR) + +from epde.interface.interface import EpdeSearch # noqa: E402 +from epde.operators.common.fitness import L2Fitness, L2LRFitness # noqa: E402 +from epde.operators.common.sparsity import LASSOSparsity, VWSRSparsity # noqa: E402 +from epde import GridTokens # noqa: E402 + + +CONFIGS_DIR = os.path.join(_THIS_DIR, 'configs') +ADAPTERS_DIR = os.path.join(_THIS_DIR, 'adapters') +RESULTS_DIR = os.path.join(_THIS_DIR, 'results') + + +# Full 2x2x2 ablation table for the three thesis-NEW contributions: +# (1) WAPE fitness -> L2LRFitness vs LEGACY L2Fitness +# (2) Instability obj -> use_pic=True swaps MOEA/D's 2nd objective +# (equation_terms_stability) vs LEGACY +# (equation_complexity_by_factors) +# (3) Novel regularizer -> VWSRSparsity (PhysicsInformedLasso, CV-weighted) +# vs LEGACY LASSOSparsity (sklearn.Lasso) +_PIPELINE_SETTINGS = { + 'legacy': {'fitness_cls': L2Fitness, 'sparsity_cls': LASSOSparsity, 'use_pic': False}, + 'wape': {'fitness_cls': L2LRFitness, 'sparsity_cls': LASSOSparsity, 'use_pic': False}, + 'instab': {'fitness_cls': L2Fitness, 'sparsity_cls': LASSOSparsity, 'use_pic': True}, + 'reg': {'fitness_cls': L2Fitness, 'sparsity_cls': VWSRSparsity, 'use_pic': False}, + 'wape_instab': {'fitness_cls': L2LRFitness, 'sparsity_cls': LASSOSparsity, 'use_pic': True}, + 'wape_reg': {'fitness_cls': L2LRFitness, 'sparsity_cls': VWSRSparsity, 'use_pic': False}, + 'instab_reg': {'fitness_cls': L2Fitness, 'sparsity_cls': VWSRSparsity, 'use_pic': True}, + 'new': {'fitness_cls': L2LRFitness, 'sparsity_cls': VWSRSparsity, 'use_pic': True}, +} + +# Default pipelines for the main Section 4.5 comparison. +PIPELINES = ('legacy', 'new') + +# Off-diagonal cells of the 2x2x2 factorial -- pass to ``run_smoke`` as the +# ``pipelines`` argument from the ablation entry point. Excludes +# ``legacy`` and ``new`` since their reps live in the default results tree +# (the 000 and 111 corners of the cube). +ABLATION_PIPELINES = ( + 'wape', 'instab', 'reg', + 'wape_instab', 'wape_reg', 'instab_reg', +) + + +def pipeline_settings(pipeline: str) -> dict: + """Return ``EpdeSearch`` kwargs for a single pipeline label. + + Recognises the original two labels (``legacy``, ``new``) and the six + off-diagonal ablation labels. Forward the returned dict directly to + :class:`EpdeSearch` (``use_pic``, ``fitness_cls``, ``sparsity_cls``). + """ + try: + return dict(_PIPELINE_SETTINGS[pipeline]) + except KeyError: + raise ValueError( + f"Unknown pipeline {pipeline!r}; expected one of {tuple(_PIPELINE_SETTINGS)}" + ) + + +@dataclass +class SystemCfg: + """Per-system configuration consumed by :func:`run_one`. + + name: short system identifier used in output filenames. + truth_tokens: canonical token set encoding the ground-truth equations. + outdir: directory to write per-rep JSON results into. + load_data: callable returning ``(coordinate_tensors, data_list, + variable_names, dimensionality)``. ``dimensionality`` is ``0`` for + ODE systems and the number of spatial axes for PDE systems. + build_extra_tokens: optional callable returning extra EPDE tokens + beyond the auto-added ``GridTokens``. Signature + ``(coords, dim) -> list``. Default: returns ``[]``. + """ + + name: str + truth_tokens: frozenset + outdir: str + load_data: Callable[[], tuple] + build_extra_tokens: Callable[[Any, int], list] = field( + default_factory=lambda: (lambda coords, dim: []) + ) + data_fun_pow: int = 3 + early_stop_on_truth: bool = True + + +def load_config(name_or_path: str) -> SystemCfg: + """Resolve a YAML config into a fully-populated :class:`SystemCfg`. + + ``name_or_path`` may be a bare system name (e.g. ``"lv"`` -- looked up + as ``configs/lv.yaml``) or an explicit path to a YAML file (relative + paths are resolved against the current working directory). + + Schema (see ``configs/.yaml`` for examples): + name: str # required + truth_equations: list[str] # required; canonicalised at load time + adapter: str # optional; defaults to ``name`` + outdir: str # optional; defaults to ``name`` (under results/) + data_fun_pow: int # optional; default 3 + early_stop_on_truth: bool # optional; default True + + Returns the populated dataclass. The adapter module is imported via + ``importlib`` from ``projects/thesis/adapters/.py``; it must + export ``load_data`` and may optionally export ``build_extra_tokens``. + """ + import yaml # local import: only required when YAML configs are used + + yaml_path = ( + name_or_path + if os.path.sep in name_or_path or name_or_path.endswith('.yaml') + else os.path.join(CONFIGS_DIR, f'{name_or_path}.yaml') + ) + yaml_path = os.path.abspath(yaml_path) + if not os.path.exists(yaml_path): + raise FileNotFoundError(f"config not found: {yaml_path}") + + with open(yaml_path, 'r', encoding='utf-8') as f: + d = yaml.safe_load(f) or {} + + name = d.get('name') + if not name: + raise ValueError(f"{yaml_path}: 'name' is required") + + from thesis_metrics import canonical_tokens + truth_equations = d.get('truth_equations') or [] + truth_tokens = canonical_tokens(truth_equations) + + adapter_name = d.get('adapter', name) + if ADAPTERS_DIR not in sys.path: + sys.path.insert(0, _THIS_DIR) + adapter_mod = importlib.import_module(f'adapters.{adapter_name}') + + if not hasattr(adapter_mod, 'load_data'): + raise AttributeError( + f"adapter {adapter_name!r} must export load_data() -> " + "(coords, data, variable_names, dim)" + ) + + outdir_rel = d.get('outdir', name) + outdir = ( + outdir_rel + if os.path.isabs(outdir_rel) + else os.path.abspath(os.path.join(RESULTS_DIR, outdir_rel)) + ) + + kwargs: dict = dict( + name=name, + truth_tokens=truth_tokens, + outdir=outdir, + load_data=adapter_mod.load_data, + ) + if hasattr(adapter_mod, 'build_extra_tokens'): + kwargs['build_extra_tokens'] = adapter_mod.build_extra_tokens + if 'data_fun_pow' in d: + kwargs['data_fun_pow'] = int(d['data_fun_pow']) + if 'early_stop_on_truth' in d: + kwargs['early_stop_on_truth'] = bool(d['early_stop_on_truth']) + return SystemCfg(**kwargs) + + +def _boundary_for(coords) -> Any: + """Return ``10%``-of-axis boundary for the supplied EPDE coordinate tensors. + + ODE problems pass a single 1-D array via ``(t,)``; the returned + boundary is a scalar ``len(t) // 10``. PDE problems pass a meshgrid + tuple where every array has the same multidimensional shape; the + returned boundary is a per-axis tuple of ``axis_size // 10``. + """ + sample = np.asarray(coords[0]) + if sample.ndim <= 1: + return max(1, len(sample) // 10) + return tuple(max(1, n // 10) for n in sample.shape) + + +def _build_truth_match_callback(cfg: 'SystemCfg') -> Callable: + """Return a per-epoch callback that stops MOEA/D once any Pareto-0 + candidate canonically matches ``cfg.truth_tokens``. + """ + from thesis_metrics import canonical_tokens, structural_success + truth = cfg.truth_tokens + + def _cb(snapshot, epoch_idx): + for entry in snapshot: + text = entry.get('text_form', '') if isinstance(entry, dict) else str(entry) + lines = [line for line in text.split('\n') if line.strip()] + try: + canon = canonical_tokens(lines) + except Exception: + continue + if structural_success(canon, truth): + return True + return False + + return _cb + + +def build_search(cfg: 'SystemCfg', pipeline_kwargs: dict) -> EpdeSearch: + """Universal EPDE search builder for the thesis Section 4.5 comparison. + + Hyperparameters are uniform across all benchmark systems; the only + branches are ODE vs PDE (deriv order, grid-token labels, boundary + shape) and the per-system data / extra-token callbacks declared on + ``cfg``. Pipeline selection is forwarded through ``pipeline_kwargs``. + """ + coords, data, variable_names, dim = cfg.load_data() + boundary = _boundary_for(coords) + max_deriv_order = (2,) if dim == 0 else (2, 4) + + grid_labels = ['x_0'] if dim == 0 else [f'x_{i}' for i in range(dim + 1)] + grid_tokens = GridTokens(grid_labels, dimensionality=dim, max_power=2) + additional_tokens = [grid_tokens] + list(cfg.build_extra_tokens(coords, dim)) + + search = EpdeSearch( + use_solver=False, + multiobjective_mode=True, + boundary=boundary, + coordinate_tensors=coords, + verbose_params={'show_iter_idx': True}, + device='cuda', + **pipeline_kwargs, + ) + search.set_preprocessor(default_preprocessor_type='FD', preprocessor_kwargs={}) + + early_stop_cb = _build_truth_match_callback(cfg) if cfg.early_stop_on_truth else None + search.set_moeadd_params(population_size=16, training_epochs=5, + early_stopping_callback=early_stop_cb) + + search.fit( + data=data, + variable_names=variable_names, + max_deriv_order=max_deriv_order, + derivs=None, + equation_terms_max_number=10, + data_fun_pow=cfg.data_fun_pow, + deriv_fun_pow=2, + additional_tokens=additional_tokens, + equation_factors_max_number={'factors_num': [1, 2], 'probas': [0.65, 0.35]}, + eq_sparsity_interval=(1e-5, 1e0), + fourier_layers=False, + ) + return search + + +def _set_seeds(seed: int) -> None: + np.random.seed(seed) + torch.manual_seed(seed) + if torch.cuda.is_available(): + torch.cuda.manual_seed_all(seed) + + +def _tokens_to_json(tokens) -> list: + """Recursively convert a canonical token structure into JSON-friendly lists.""" + def factor(f): + name, params = f + return [name, sorted(([k, v] for k, v in params), key=lambda p: p[0])] + + def term(t): + return sorted([factor(f) for f in t], key=lambda f: (f[0], repr(f[1]))) + + out = [] + for target, rhs in tokens: + out.append([ + term(target), + sorted((term(t) for t in rhs), key=lambda x: repr(x)), + ]) + return sorted(out, key=lambda x: repr(x)) + + +def _discovery_epochs(final_token_sets, pareto_history) -> list: + """For each canonical token set in ``final_token_sets``, return the + first epoch index (0-based) in ``pareto_history`` whose Pareto-0 + snapshot contains a solution with the same canonical structure. + """ + from thesis_metrics import canonical_tokens + + snapshot_canon = [] + for epoch_snapshot in pareto_history: + per_epoch = [] + for sol_record in epoch_snapshot: + text = sol_record.get('text_form', '') if isinstance(sol_record, dict) else str(sol_record) + lines = [line for line in text.split('\n') if line.strip()] + per_epoch.append(canonical_tokens(lines)) + snapshot_canon.append(per_epoch) + + epochs = [] + for target in final_token_sets: + first = None + for epoch_idx, epoch_canon in enumerate(snapshot_canon): + if any(c == target for c in epoch_canon): + first = epoch_idx + break + epochs.append(first) + return epochs + + +def _extract_discovered(search: EpdeSearch) -> list: + """Return all solutions from the non-dominated Pareto level.""" + eqs = search.equations(only_print=False, only_str=True, num=1) + if not eqs: + return [] + if isinstance(eqs[0], list): + level0_solutions = eqs[0] + else: + level0_solutions = eqs + + out = [] + for solution in level0_solutions: + if not isinstance(solution, str): + solution = str(solution) + out.append([line for line in solution.split('\n') if line.strip()]) + return out + + +def _extract_objectives(search: EpdeSearch) -> list: + """Return per-solution objective vectors aligned with ``_extract_discovered``.""" + try: + level0 = search.optimizer.pareto_levels.levels[0] + except Exception: + return [] + out = [] + for sol in level0: + try: + obj = sol.obj_fun.tolist() if hasattr(sol.obj_fun, 'tolist') else list(sol.obj_fun) + except Exception: + obj = None + out.append(obj) + return out + + +def run_one(system_cfg: SystemCfg, pipeline: str, seed: int) -> dict: + """Run a single (system, pipeline, seed) repetition. + + Returns a dict suitable for JSON serialization. Exceptions are caught + and recorded as ``error`` and ``traceback`` fields so a failing rep + does not kill the batch. + """ + from thesis_metrics import canonical_tokens, hamming, structural_success + + pipeline_kwargs = pipeline_settings(pipeline) + _set_seeds(seed) + + record: dict = { + 'system': system_cfg.name, + 'pipeline': pipeline, + 'seed': seed, + 'pipeline_kwargs': { + 'use_pic': pipeline_kwargs['use_pic'], + 'fitness_cls': pipeline_kwargs['fitness_cls'].__name__, + 'sparsity_cls': pipeline_kwargs['sparsity_cls'].__name__, + }, + } + + t0 = time.time() + try: + search = build_search(system_cfg, pipeline_kwargs) + elapsed = time.time() - t0 + solutions_text = _extract_discovered(search) + objectives_per_solution = _extract_objectives(search) + per_solution_tokens = [canonical_tokens(sol) for sol in solutions_text] + pareto_history = list(getattr(search, 'pareto_history', [])) + if per_solution_tokens: + hammings = [hamming(c, system_cfg.truth_tokens) for c in per_solution_tokens] + best_idx = int(min(range(len(hammings)), key=lambda i: hammings[i])) + discovery_epochs = _discovery_epochs(per_solution_tokens, pareto_history) + best_objectives = ( + objectives_per_solution[best_idx] + if best_idx < len(objectives_per_solution) else None + ) + record.update({ + 'runtime_sec': elapsed, + 'n_pareto_solutions': len(per_solution_tokens), + 'discovered_text_per_solution': solutions_text, + 'discovered_text': solutions_text[best_idx], + 'discovered_tokens_per_solution': [_tokens_to_json(c) for c in per_solution_tokens], + 'discovered_tokens': _tokens_to_json(per_solution_tokens[best_idx]), + 'truth_tokens': _tokens_to_json(system_cfg.truth_tokens), + 'hamming_per_solution': hammings, + 'hamming': hammings[best_idx], + 'discovery_epoch_per_solution': discovery_epochs, + 'discovery_epoch': discovery_epochs[best_idx], + 'n_epochs': len(pareto_history), + 'objectives_per_solution': objectives_per_solution, + 'objectives': best_objectives, + 'structural_success': any( + structural_success(c, system_cfg.truth_tokens) for c in per_solution_tokens + ), + }) + else: + record.update({ + 'runtime_sec': elapsed, + 'n_pareto_solutions': 0, + 'discovered_text_per_solution': [], + 'discovered_text': [], + 'discovered_tokens_per_solution': [], + 'discovered_tokens': [], + 'truth_tokens': _tokens_to_json(system_cfg.truth_tokens), + 'hamming_per_solution': [], + 'hamming': None, + 'objectives_per_solution': [], + 'objectives': None, + 'structural_success': False, + }) + except Exception as exc: # pragma: no cover - smoke-time diagnostic + record.update({ + 'runtime_sec': time.time() - t0, + 'error': repr(exc), + 'traceback': traceback.format_exc(), + 'n_pareto_solutions': 0, + 'discovered_text_per_solution': [], + 'discovered_text': [], + 'discovered_tokens': [], + 'hamming': None, + 'objectives_per_solution': [], + 'objectives': None, + 'structural_success': False, + }) + return record + + +def _resolve_out_root(system_cfg: SystemCfg, outdir: Optional[str]) -> str: + """Resolve the final output directory for a batch. + + Default (``outdir is None``) -> ``system_cfg.outdir`` (typically + ``projects/thesis/results/``). + Absolute path -> used as-is. + Bare tag (e.g. ``ablation_v2``) -> ``results//``, so a + tagged sweep across all systems + stays grouped under one folder + (``results//lv``, .../lorenz, ...) + and the aggregator can scan a tag in + one glob. + """ + if outdir is None: + return system_cfg.outdir + if os.path.isabs(outdir): + return outdir + return os.path.join(RESULTS_DIR, outdir, system_cfg.name) + + +def run_smoke( + system_cfg: SystemCfg, + reps: int = 3, + pipelines: Iterable[str] = PIPELINES, + seed_base: int = 0, + resume: bool = True, + outdir: Optional[str] = None, +) -> None: + """Run ``reps`` × len(pipelines) repetitions and write JSON per rep. + + With ``resume=True`` (default) any ``(pipeline, rep)`` whose target JSON + already exists and parses as JSON is skipped. Pass ``resume=False`` to + overwrite. See :func:`_resolve_out_root` for ``outdir`` semantics. + """ + out_root = _resolve_out_root(system_cfg, outdir) + os.makedirs(out_root, exist_ok=True) + for pipeline in pipelines: + for rep in range(reps): + seed = seed_base + rep + out_path = os.path.join(out_root, f"{pipeline}_rep{rep:02d}.json") + if resume and os.path.exists(out_path): + try: + with open(out_path, 'r', encoding='utf-8') as fh: + json.load(fh) + print(f"\n========== {system_cfg.name} / {pipeline} / rep {rep} -- " + f"skipping (resume; {out_path} exists) ==========") + continue + except (json.JSONDecodeError, OSError) as exc: + print(f"[resume] {out_path} unreadable ({exc!r}); re-running rep") + print(f"\n========== {system_cfg.name} / {pipeline} / rep {rep} (seed={seed}) ==========") + record = run_one(system_cfg, pipeline, seed) + with open(out_path, 'w', encoding='utf-8') as fh: + json.dump(record, fh, indent=2, default=str) + status = 'OK' if 'error' not in record else 'FAIL' + ham = record.get('hamming') + epoch = record.get('discovery_epoch') + n_ep = record.get('n_epochs') + epoch_str = f"epoch={epoch}/{n_ep}" if epoch is not None else "epoch=?" + print(f" -> {status} hamming={ham} {epoch_str} time={record.get('runtime_sec', 0.0):.1f}s") + print(f" -> saved {out_path}") From 777d6db45ecac0158dc724eb59d09d6d9b12483a Mon Sep 17 00:00:00 2001 From: Gromwud Date: Wed, 20 May 2026 14:07:11 +0300 Subject: [PATCH 7/8] trig tokens: quantized-freq structural identity Replace the trig-only `freq`-stripping branch in `Term.factors_labels` and the matching defensive code in `EqRightPartSelector.simplify_equation` with a uniform `Factor.structural_label` that bucketises continuous- tolerance params (e.g. trig `freq`) via `equality_ranges`. Same quantization powers `factors_labels_without_power` for the simplify common-factor scan. `cache_label` is unchanged and continues to key the tensor cache. No behavioural change for the thesis 14 systems: every system uses a narrow `freq=(v-eps, v+eps)` interval, so all sampled freq values land in bucket 0 and produce the same structural identity as the prior freq-stripping logic. --- epde/operators/common/right_part_selection.py | 115 ++++++++---------- epde/structure/factor.py | 42 +++++++ epde/structure/main_structures.py | 38 ++---- 3 files changed, 107 insertions(+), 88 deletions(-) diff --git a/epde/operators/common/right_part_selection.py b/epde/operators/common/right_part_selection.py index 5081b971..fe959f23 100644 --- a/epde/operators/common/right_part_selection.py +++ b/epde/operators/common/right_part_selection.py @@ -125,69 +125,60 @@ def simplify_equation(self, objective: Equation): nonzero_terms.append(objective.structure[objective.target_idx]) equation_terms = [term.factors_labels_without_power for term in nonzero_terms] - # If amount nonzero terms is more than one -- get their intersection - if len(equation_terms) > 1: - common_factors = list(frozenset.intersection(*equation_terms)) - if len(common_factors) > 0: - for common_factor in common_factors: - # Find if this intersection in the same dimension (i.e. trigonometry functions) + it's minimal order - min_order = np.inf - common_dim = [] - for term in nonzero_terms: - for factor in term.structure: - if len(factor.params) == 1: - factor_label = (factor.cache_label[0]) + if len(equation_terms) <= 1: + return False + common_factors = list(frozenset.intersection(*equation_terms)) + if not common_factors: + return False + + for common_factor in common_factors: + # Min power across the matching factor in every nonzero term. + min_order = np.inf + for term in nonzero_terms: + for factor in term.structure: + if factor.structural_label_without_power == common_factor: + if factor.cache_label[1][0] < min_order: + min_order = factor.cache_label[1][0] + + # Reduce order of common factor in every term; drop zero-power factors. + max_iter = 100 + for term in nonzero_terms: + factors_simplified = [] + for factor in term.structure: + if factor.structural_label_without_power == common_factor: + for i, value in enumerate(factor.params_description): + if factor.params_description[i]["name"] == "power": + factor.params[i] -= min_order + if factor.params[i] == 0: + factors_simplified.append(factor) else: - factor_label = (factor.cache_label[0], (factor.cache_label[1][-1])) - if factor_label == common_factor: - if len(factor.params) > 1: - common_dim.append(factor.params[-1]) - if factor.cache_label[1][0] < min_order: - min_order = factor.cache_label[1][0] - if len(set(common_dim)) < 2: - # If dimension is the same -- reduce order of terms' factor - max_iter = 100 - for term in nonzero_terms: - factors_simplified = [] - for factor in term.structure: - if len(factor.params) == 1: - factor_label = (factor.cache_label[0]) - else: - factor_label = (factor.cache_label[0], (factor.cache_label[1][-1])) - if factor_label == common_factor: - for i, value in enumerate(factor.params_description): - if factor.params_description[i]["name"] == "power": - factor.params[i] -= min_order - if factor.params[i] == 0: - factors_simplified.append(factor) - else: - continue - term.structure = [factor for factor in term.structure if factor not in factors_simplified] - term.reset_saved_state() - - # If term's order became zero -- replace term. - # Cap retries so a constrained token pool can't - # deadlock the optimizer (same hazard fixed in - # ``enforce_rps_uniqueness``). - attempts = 0 - while attempts < max_iter: - empty = len(term.structure) == 0 - not_meaningful = not term.contains_meaningful() - signatures = {t.factors_labels for t in objective.structure} - duplicate = len(signatures) != len(objective.structure) - if not (empty or not_meaningful or duplicate): - break - term.randomize() - attempts += 1 - - # Structure changed: invalidate stale fitness / - # weights / AIC caches while leaving RPS to the - # caller's outer loop. - try: - objective.reset_state(reset_right_part=False) - except TypeError: - objective.reset_state() - return True + continue + term.structure = [factor for factor in term.structure if factor not in factors_simplified] + term.reset_saved_state() + + # If term's order became zero -- replace term. + # Cap retries so a constrained token pool can't + # deadlock the optimizer (same hazard fixed in + # ``enforce_rps_uniqueness``). + attempts = 0 + while attempts < max_iter: + empty = len(term.structure) == 0 + not_meaningful = not term.contains_meaningful() + signatures = {t.factors_labels for t in objective.structure} + duplicate = len(signatures) != len(objective.structure) + if not (empty or not_meaningful or duplicate): + break + term.randomize() + attempts += 1 + + # Structure changed: invalidate stale fitness / + # weights / AIC caches while leaving RPS to the + # caller's outer loop. + try: + objective.reset_state(reset_right_part=False) + except TypeError: + objective.reset_state() + return True return False def use_default_tags(self): diff --git a/epde/structure/factor.py b/epde/structure/factor.py index 1b90232a..bcff0430 100644 --- a/epde/structure/factor.py +++ b/epde/structure/factor.py @@ -273,6 +273,48 @@ def cache_label(self): cache_label = factor_params_to_str(self) return cache_label + def _quantized_params(self, drop_power: bool = False) -> tuple: + """Return params with continuous-tolerance ones quantized into bucket + indices and exact-equality ones passed through. Continuous params + (those with ``equality_ranges[name] > 0``, e.g. trig ``freq``) get + ``int((v - bounds[0]) / equality_ranges[name])``; exact-equality + params (``power``, ``dim``) stay numeric. When ``drop_power=True`` + the param named ``'power'`` is omitted from the result tuple. + """ + parts = [] + for i in range(len(self.params)): + name = self.params_description[i]['name'] + if drop_power and name == 'power': + continue + v = self.params[i] + tol = self.equality_ranges.get(name, 0) + if tol > 0: + origin = self.params_description[i]['bounds'][0] + parts.append(int((v - origin) / tol)) + else: + parts.append(v) + return tuple(parts) + + @property + def structural_label(self): + """Hashable canonical identity for structural dedup. + + Sits next to ``cache_label`` (which keys the tensor cache and + must stay exact). Continuous params are quantized into bucket + indices so set-based dedup and ``Factor.__eq__``'s tolerance + comparison agree. + """ + return (self.cache_label[0], self._quantized_params(drop_power=False)) + + @property + def structural_label_without_power(self): + """``structural_label`` with the ``power`` param dropped. + + Used by ``simplify_equation`` to find shared factors across + terms regardless of their individual powers. + """ + return (self.cache_label[0], self._quantized_params(drop_power=True)) + @property def name(self): form = self.label + '{' diff --git a/epde/structure/main_structures.py b/epde/structure/main_structures.py index 761b6fda..7e31b570 100644 --- a/epde/structure/main_structures.py +++ b/epde/structure/main_structures.py @@ -399,39 +399,25 @@ def __deepcopy__(self, memo=None): @property def factors_labels_without_power(self) -> frozenset: - """Return a frozenset of factor labels with the power parameter dropped. + """Return a frozenset of structural labels with the ``power`` param dropped. - Each entry is either the cache label tuple or its head plus the trailing - param (when the factor has more than one parameter). Used to compare - terms for structural identity ignoring power differences. + Identity is delegated to ``Factor.structural_label_without_power``, + which quantizes continuous-tolerance params (e.g. trig ``freq``) + into bucket indices so structural dedup stays consistent with + ``Factor.__eq__``. """ - described = set() - for factor in self.structure: - if len(factor.params) == 1: - factor_label = (factor.cache_label[0]) - else: - factor_label = (factor.cache_label[0], (factor.cache_label[1][-1])) - described.add(factor_label) - described = frozenset(described) - return described + return frozenset(factor.structural_label_without_power for factor in self.structure) @property def factors_labels(self) -> frozenset: - """Return a frozenset of canonical labels for each factor in the term. + """Return a frozenset of structural labels for each factor in the term. - Trigonometric factors collapse the ``freq`` parameter (kept fungible - across small frequency ranges); other factors use ``factor.cache_label`` - directly. Used as a hashable identity for set/membership checks. + Identity is delegated to ``Factor.structural_label``, which + bucketises continuous-tolerance params (e.g. trig ``freq``) so + within-bucket differences don't fracture structural identity. + Used as a hashable identity for set/membership checks. """ - described = set() - for factor in self.structure: - if factor.ftype == 'trigonometric': - label = (factor.cache_label[0], tuple(factor.cache_label[1][i] for i, param in factor.params_description.items() if param['name'] != 'freq')) - described.add(label) - else: - described.add(factor.cache_label) - described = frozenset(described) - return described + return frozenset(factor.structural_label for factor in self.structure) @property def term_label_without_power(self): From b4e8766bb13fb9f992b736cb0e5081189cee2c10 Mon Sep 17 00:00:00 2001 From: Gromwud Date: Wed, 20 May 2026 14:07:36 +0300 Subject: [PATCH 8/8] deepxde: defer import-time backend banner `import deepxde` prints a multi-line backend banner ("Using backend: pytorch ...") on first load. The eager `from .deepxde_integration import DeepXDEAdapter` in `epde/integrate/__init__.py` and the top-level import in `fitness.py` meant any `import epde` triggered that banner even when no DeepXDE solver is in use (e.g. the legacy L2 / L2LR fitness paths). Drop the eager imports; expose `DeepXDEAdapter` via a PEP 562 `__getattr__` on `epde.integrate` so the name still resolves on first access, but the banner only fires when DeepXDE is actually requested (via `DeepXDEBasedFitness.apply()`'s existing lazy import at line 436). --- epde/integrate/__init__.py | 12 +++++++++++- epde/operators/common/fitness.py | 5 ++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/epde/integrate/__init__.py b/epde/integrate/__init__.py index 95f3cd5e..e6f104b6 100644 --- a/epde/integrate/__init__.py +++ b/epde/integrate/__init__.py @@ -2,4 +2,14 @@ from .bop import BOPElement, BoundaryConditions from .pinn_integration import SolverAdapter from .numeric_integration import OdeintAdapter -from .deepxde_integration import DeepXDEAdapter \ No newline at end of file + + +# ``deepxde_integration`` does ``import deepxde``, which prints a backend +# banner on first load. Defer that until the DeepXDE adapter is actually +# requested so plain ``import epde`` / ``from epde.integrate import +# SolverAdapter`` stays quiet. +def __getattr__(name): + if name == 'DeepXDEAdapter': + from .deepxde_integration import DeepXDEAdapter + return DeepXDEAdapter + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") \ No newline at end of file diff --git a/epde/operators/common/fitness.py b/epde/operators/common/fitness.py index 304b2481..bc331781 100644 --- a/epde/operators/common/fitness.py +++ b/epde/operators/common/fitness.py @@ -13,7 +13,10 @@ import matplotlib.pyplot as plt from matplotlib import cm -from epde.integrate import SolverAdapter, DeepXDEAdapter +from epde.integrate import SolverAdapter +# DeepXDEAdapter is imported lazily inside DeepXDEBasedFitness.apply() to +# avoid triggering deepxde's import-time backend banner when no DeepXDE +# solver is used (e.g. legacy L2/L2LR fitness paths). from epde.structure.main_structures import SoEq, Equation from epde.operators.utils.template import CompoundOperator import epde.globals as global_var